Integrations
Webhooks and Email
Tell an external system, or a person, when something happens in DFIRe.
Overview
A notifier watches for events and delivers on one of two channels. A Webhook notifier sends an HTTP POST request to a URL you configure. Use it for SIEMs, ticketing tools, paging services, or custom automation. An Email notifier sends a message to the people you name. See Email notifiers.
Webhook Secrets let you store sensitive values like API keys and reference them in webhook payloads.
Notifiers live in Settings > Webhooks & Email. The page has two tabs: Notifiers (for the notifiers themselves) and Webhook secrets (for stored credentials).
Outgoing Webhooks
Outgoing webhooks send HTTP POST requests to a configured URL when specific events occur in DFIRe. Use them to integrate with notification services (Pushover, PagerDuty), ticketing systems (Jira, ServiceNow), SIEMs, or custom automation.
Creating an Outgoing Webhook
- Click "Add notifier" in the Outgoing Webhooks section
-
Configure basic settings
- Name: Descriptive name for the webhook
- Endpoint URL: The URL to receive webhook payloads. DFIRe refuses a loopback or link-local address, including the cloud metadata address. It also refuses its own hostnames, since a webhook to itself would loop. Other private network addresses are accepted, so a webhook can reach another service on the same network
- Description: Optional description of what this webhook does
-
Select trigger events
Choose which events should trigger this webhook.
-
Optionally set a content filter
Restrict which events fire the webhook by trigger tag (for runbook and automation events) or by timeline content. See Trigger tags.
- Configure authentication
- Customize the payload template
- Save the webhook
Email notifiers
Every notifier has a channel, chosen at the top of the editor. A Webhook notifier sends an HTTP request to a URL. An Email notifier sends a message to the people you name. Both channels share everything else: the trigger events, the content filter, the value mappings and the same template variables. While you write, switching the channel keeps what you typed on the other side, so you lose nothing by trying one and going back. A saved notifier keeps the fields of the channel it was saved on.
An email notifier needs Email Delivery configured. Until you select a provider there, the editor offers the Webhook channel only, and the Email channel names what to configure. A notifier already on the Email channel stays on it, and records a failed delivery instead of sending.
DFIRe sends the body inside the installation's layout: a header above it and a footer below. The message carries an HTML part and a plain text part. The plain text part is the Markdown source rather than a rendering of it.
| Field | What to enter |
|---|---|
| Recipients | One line, separated by commas. Mix typed addresses and variables in any order, for example [email protected], {{email.case_lead}}. A variable with nobody behind it renders as nothing, and DFIRe still writes to the addresses beside it. |
| Subject | A single line. DFIRe refuses a subject that carries a line break. |
| Body | Written in Markdown: headings, lists, tables, links, images, fenced code and task lists. DFIRe gives a table borders and padding that mail clients show, since they read no stylesheet. HTML written inside it is cleaned the same way the layout is. Leave it empty to send the subject alone. |
| Plain text only | Off by default. On, the notifier sends the text part alone, with no header, footer or logo. Use it for a destination that parses the message rather than reads it, such as a pager gateway or a ticketing inbox. |
Seven variables resolve to addresses, and every event carries all of them:
{{email.user}}, the address of whoeveruser.*names{{email.account}}, the address of the account an account event is about{{email.case_lead}}, the lead investigator on the case{{email.case_team}}, the lead investigator and the investigators{{email.message_recipient}}, the person a notification is addressed to{{email.superusers}}, every active superuser{{email.tenant_owner}}, the organization contact address
The editor offers these beside the Recipients field, and the full list is under EMAIL.*.
How a value renders
A variable renders as typed. An asterisk in a case title arrives as an asterisk, and a link typed into one stays text. Nobody puts markup into a message by naming a case after it.
Add |markdown to render a value that holds Markdown, as in {{case.description|markdown}}. Four fields hold Markdown: case.description, event.description, event.details and message.body. DFIRe cleans a rendered value the same way it cleans the rest of the message. The modifier applies to the body and changes nothing in the subject, the recipients or a webhook payload.
Who may receive
DFIRe writes to the address of an active user account, and to any address whose domain the Email Delivery page lists. It refuses every other address. A refusal goes on the delivery record with its reason, and nothing reaches that address. When DFIRe accepts nobody, the delivery fails and the record says why. The editor's preview marks each rendered address, so a refusal shows before you save the notifier.
A stored secret never reaches a mailbox. On the email channel, a {{secret.name}} reference renders as nothing.
Preview and test
Select Preview to render the recipients, the subject and the message against sample data. The addresses that belong to one person resolve to your own account, so a preview never shows somebody else's address. The superuser and organization contact addresses resolve for real. The preview marks each address accepted or refused, with the reason beside a refused one.
The preview shows the assembled message in a frame, with its plain text part on its own tab. Read what the recipient reads before you save the notifier. A notifier set to plain text only has no HTML part, so the preview shows the text alone.
Select Test to send one real message, with the same substitution the preview used. The result names the addresses it reached and the ones it refused.
What the delivery log shows
Open a notifier's delivery history for the recipients, the subject, and each refused address with its reason. A message waiting for the per-minute cap shows as retrying, with the cap named as the reason. An email delivery carries no HTTP status code, because no server answers it. DFIRe does not keep the message itself.
Trigger Events
Select one or more events that should trigger the webhook. The editor groups them into the same families this table uses:
| Event | Description |
|---|---|
| Cases and evidence | |
| Incident Created | A new incident case was created |
| Investigation Created | A new investigation case was created |
| Case Closed | A case was closed |
| Case Archived | A case was archived |
| Case Reopened | A closed case was reopened |
| Investigation Escalated to Incident | An investigation was escalated to incident mode |
| Severity/Priority Escalated | Case severity was increased (e.g., medium to high) |
| Severity/Priority Downgraded | Case severity was decreased |
| Incident Phase Changed | Incident moved to a different response phase |
| Timeline Event Created | A new timeline event was added to a case |
| CAN Report Updated | A new CAN (Conditions, Actions, Needs) report version was created |
| Case Team Changed | A case gained or lost a lead investigator, an investigator or a viewer. Read meta.change for which of the five it was, and meta.usernames for the people it moved. |
| Evidence Added | New evidence item was added to a case |
| Evidence Status Changed | Evidence item's investigation step changed |
| Compliance timers | |
| Timer at 50% (Half Time Warning) | Compliance timer reached 50% of its duration |
| Timer at 25% (Quarter Time Warning) | Compliance timer reached 75% elapsed (25% remaining) |
| Timer at 10% (Critical Warning) | Compliance timer reached 90% elapsed (10% remaining) |
| Timer Breached (Deadline Passed) | Compliance timer deadline has passed |
| Indicators | |
| IOC Added to Case | An indicator was associated with a case |
| IOC Classification Changed | An indicator's classification was changed (e.g., unknown to malicious) |
| IOC Classified as Malicious | An indicator was classified as malicious |
| IOC Classified as Suspicious | An indicator was classified as suspicious |
| IOC Classified as Benign | An indicator was classified as benign |
| IOC Published | An indicator was published to TAXII/MISP feeds |
| IOC Unpublished | A published indicator was unpublished |
| IOC Revoked | An indicator was revoked (no longer considered valid) |
| IOC Unrevoked | A revoked indicator was restored |
| IOC Cross-Case Correlation | An indicator added to a case was already present in other cases |
| Playbooks and automation | |
| Runbook Step Completed | A runbook step was marked complete. Its trigger tag travels with the event, so a content filter can narrow delivery to the tagged steps. Filter by its trigger tag with the content filter. |
| Automation Rule Triggered | An automation rule's "Fire a webhook" action matched. Filter by its trigger tag with the content filter. |
| Playbook Review Due | A playbook's review deadline is close, or has passed. See Playbook review reminders. |
| Accounts and system | |
| User Notification | An in-app notification was sent to a user. High volume: this fires for every notification. |
| User Mentioned | A user was mentioned with @. Fires only for mentions, so an external target can subscribe to it without the full notification volume. |
| User Logged In | Somebody signed in. meta.login_method is password or sso, and meta.second_factor names the second factor when the sign-in needed one. |
| Login Failed | DFIRe refused a sign-in, and meta.reason says why. The ACCOUNT.* variables name the account the attempt was for, even when no such account exists. |
| User Created | An administrator created an account, or a first SSO sign-in did. meta.created_via says which. |
| User Deactivated or Roles Changed | An administrator switched an account off or back on, or gave it a different set of roles. meta.change says which, and a role change also carries meta.roles_before and meta.roles_after. |
| API Key Created | DFIRe issued an API key value for an account. The event names the key in meta.key_name and never carries its value. |
| Report Finalized | An investigation report reached version 1.0 and is ready for the stakeholder. See REPORT.* variables. |
| Backup Finished | A backup run ended, on both outcomes, whether someone started it or the schedule did. See BACKUP.* variables. |
| DFIRe Update Available | The license server offers a newer DFIRe release. It fires once per version, so the daily check does not repeat a release you already know about. See UPDATE.* variables. |
| Test Event (Manual Testing) | Used for testing webhook configuration |
Trigger tags and the content filter
Some events carry a trigger tag. A short identifier that lets a single event type fan out to different webhooks. Three features in DFIRe emit tagged events:
- IOC triggers. Custom response buttons on IOC Intelligence pages, defined under Settings → IOC Enrichment. Each one appears in the event list above as its own IOC Trigger: name entry; subscribe to the specific trigger you want. No content filter is needed. Each trigger is its own event. See Configuration. Webhook Triggers.
- Runbook triggers. A tag on a runbook step. Marking the step complete fires the Runbook Step Completed event carrying that tag.
- Automation triggers. The trigger tag on an automation rule's "Fire a webhook" action. The rule fires the Automation Rule Triggered event carrying that tag.
For the Runbook Step Completed and Automation Rule Triggered events, the content filter is a regex matched against the trigger tag. Leave it empty to fire on every tag, or set a specific tag. Or a pattern such as account_disabled|notify_soc. To choose which ones reach this webhook. Because both events filter on the same kind of tag, one webhook can serve several rules, or be shared by a runbook step and an automation rule that carry the same tag.
For Timeline Event Created, the content filter instead matches the event's title and description. For example, @page fires the webhook only for timeline entries containing "@page".
For the five account events, the content filter matches the username of the account the event is about. On a failed login that is the username somebody tried, which may belong to no account. Leave it empty to match every account.
A content filter is at most 255 characters. Matching ignores case. DFIRe reads the shape of the pattern and refuses one that can take unlimited time to match, because such a filter stalls webhook delivery. DFIRe refuses these shapes:
- A repeated group whose contents repeat or offer alternatives, such as
(a+)+,(a+){15}or(a|aa)+. - Two repeats written next to each other, such as
a*a*bor(a*)(a*)b. - More than two open-ended repeats, such as
^a.*a.*a.*b$, or more than one in a pattern that does not start with^, such asa.*a.*b. - An inline flag, such as
(?i)or(?x).
Because the check reads the shape rather than trying the pattern, it also refuses some patterns that would match quickly. Write the filter as a keyword match. DFIRe accepts patterns such as ransom, ransom|extortion, (malware|phish)ing, ^CASE-\d{4}-\d{3}$ and \d{1,3}\.\d{1,3}.
Playbook review reminders
The Playbook Review Due event reports that a playbook needs reading through again. It does not report a review. Nothing about it says a person looked at the playbook, only that its deadline is close or has passed.
A daily job checks every playbook against its deadline. Each reminder day configured under Settings → Playbook Policy fires the event once, and the deadline itself fires it once more. A playbook with reminders at 30 and 7 days produces three events in a review cycle. One a month out, one a week out, one on the day.
Each threshold fires once per review cycle. Recording a review, or changing the review period, starts a new cycle and the reminders begin again. If DFIRe was down when a threshold passed, the next run sends one reminder for the most urgent threshold it has crossed. It marks the ones it skipped as sent. An overdue playbook produces no backlog of missed warnings.
A webhook subscribed after a threshold has fired does not receive that threshold. It receives the next one, in the same way as compliance timer subscriptions.
Delivery is at least once. The payload carries playbook.event_id, and it stays the same across every retry of one reminder. A receiver that may see a duplicate can drop it on that value. The PLAYBOOK.* variables list what a reminder carries.
Authentication
DFIRe supports four authentication methods for outgoing webhooks:
| Method | Description |
|---|---|
| None | No authentication. Use when the endpoint doesn't require auth or when credentials are in the payload. |
| Basic Auth | HTTP Basic Authentication. Provide username and password. |
| Bearer Token | Sends an Authorization: Bearer <token> header. Provide the token value. |
| Custom Headers | Add custom HTTP headers, for example X-API-Key. Header values are encrypted at rest, but they are returned when the webhook is opened, so anyone who can view the webhook can read them. Keep API keys and tokens in Webhook Secrets and put {{secret.name}} in the header value instead. A secret's value is never shown again after you save it. |
Payload Templates
Customize the JSON payload sent to your endpoint using template variables. If you leave the payload template empty, DFIRe sends a default payload: the event type, the timestamp and the notifier name, then every variable category the event carries. A stored secret is never part of it.
Template Variable Syntax
Use double curly braces to insert variable values:
{{category.variable}}- Insert the raw value{{category.variable|mapped}}- Apply value mappings before inserting
Available Variables
Variables are organized by category:
META.* (Request Metadata)
meta.event_type | The event that triggered this webhook |
meta.timestamp | When the event occurred (ISO 8601) |
meta.webhook_name | Name of this webhook configuration |
meta.base_url | DFIRe instance base URL |
meta.case_url | Direct URL to the case |
meta.item_url | Direct URL to the evidence item (if applicable) |
meta.ioc_url | Direct URL to the indicator (if applicable) |
meta.tenant_name | Organization name |
meta.tenant_uuid | Tenant identifier |
meta.environment | Environment (production, development) |
meta.previous_severity | Previous severity (for severity change events) |
meta.new_severity | New severity (for severity change events) |
meta.old_classification | Previous classification (for IOC classification change events) |
meta.new_classification | New classification (for IOC classification change events) |
meta.indicator_id | Indicator database ID (for IOC events) |
meta.indicator_value | Indicator value (for IOC events) |
meta.indicator_type | Indicator STIX type (for IOC events) |
CASE.* (Case Information)
case.id | Case UUID |
case.case_number | Case number (e.g., CASE-2026-001) |
case.title | Case title |
case.description | Case description |
case.status | Case status (open, closed, archived) |
case.severity | Severity level (info, low, medium, high, critical) |
case.case_mode | Case mode (investigation, incident) |
case.phase_name | Current incident phase name |
case.phase_order | Current incident phase order |
case.case_type_name | Case type name |
case.lead_investigator | Lead investigator username |
case.created_at | Case creation timestamp |
case.updated_at | Last update timestamp |
EVENT.* (Timeline Event Information)
event.id | Event UUID |
event.event_type | Event type (MANUAL, AUTO, etc.) |
event.title | Event title/subject |
event.description | Event description |
event.subject | Event subject |
event.details | Event details |
event.is_manual | Whether this is a manual entry |
event.actor_username | User who created the event |
event.event_datetime | When the event occurred |
event.created_at | When the event was recorded |
event.metadata | Additional event metadata (JSON) |
ITEM.* (Evidence Item Information)
item.uuid | Evidence item UUID |
item.name | Evidence item name |
item.item_type_name | Evidence type name |
item.status_name | Current investigation step |
item.location | Storage location |
item.created_at | When the item was added |
IOC.* (Indicator Information)
ioc.id | Indicator database ID |
ioc.value | Indicator value (for example an IP address, domain, or hash) |
ioc.stix_type | STIX type (for example ipv4-addr, domain-name, file) |
ioc.classification | Classification (unknown, benign, suspicious, malicious) |
ioc.confidence | Confidence level (low, medium, high) |
ioc.tags | Tags as a list |
ioc.public_notes | Public notes on the indicator |
ioc.first_seen | First seen timestamp |
ioc.last_seen | Last seen timestamp |
ioc.created_at | When the indicator was created |
ioc.stix_sco | The indicator as a STIX 2.1 Cyber Observable object |
ioc.stix_sdo | The indicator as a STIX 2.1 Indicator object |
For a classification change, the previous and new values are in
meta.old_classification and meta.new_classification.
USER.* (The Person the Event Names)
On a case event this is the lead investigator. Four events name someone else:
- a timeline event, its actor
- a CAN report update, the author of the version
- a runbook step completion, whoever ticked the step
- a user notification or mention, the recipient
An event with no case, such as an IOC publication or the playbook review reminder, carries nothing here.
On an account event this is whoever acted. On a sign-in that is the person who signed in. On an account an administrator created or a key they issued, it is the administrator. A failed login carries nothing here, because nobody proved who they were. The account the event is about is ACCOUNT.*.
user.id | User ID |
user.username | Username |
user.email | Email address |
user.first_name | First name |
user.last_name | Last name |
user.phone_number | Phone number, when set |
user.timezone | Preferred timezone, when set |
user.slack_user_id | Slack member ID, when the account is linked |
user.slack_username | Slack username, when the account is linked |
ACCOUNT.* (The Account an Account Event Is About)
Available on the five account events. These variables describe the account
the event is about. That is not always the person who acted. When an
administrator creates an account or issues a key, user.* names the
administrator and account.* names the account.
account.id | User ID, empty on a failed login for an account that does not exist |
account.username | Username. On a failed login, the username that was tried |
account.email | Email address |
account.first_name | First name |
account.last_name | Last name |
account.phone_number | Phone number, when set |
account.timezone | Preferred timezone, when set |
account.slack_user_id | Slack member ID, when the account is linked |
account.slack_username | Slack username, when the account is linked |
account.is_superuser | True for a System Root account |
account.is_active | False once the account is switched off |
account.roles | The account's roles, comma separated |
A failed login for a username that belongs to no account fills
account.username with what was typed and leaves the rest empty.
MESSAGE.* (Notification)
Present on the User Notification and User Mentioned events. The
user.* category then describes the person being notified.
message.title | Notification title |
message.body | Notification body |
message.type | Notification type (for example CASE_ASSIGNED, MENTION, TIMER_WARNING) |
message.sender_name | Who or what raised the notification |
message.target_model | What the notification points at (Case, Item, Attachment) |
message.target_id | ID or UUID of that target |
message.case_id | Case ID, when the notification belongs to a case |
message.created_at | When the notification was raised |
CAN.* (CAN Report)
can.conditions | Conditions section |
can.actions | Actions section |
can.needs | Needs section |
can.version | Version number of this CAN report |
can.created_by | Username of the author |
can.created_at | When this version was created |
RUNBOOK_STEP.* (Runbook Step Completed)
Runbooks attach to an evidence item or to a case action. A case action has
no evidence item, so the item.* variables render as nothing for a
step completed on one.
runbook_step.order | Position of the step in the runbook |
runbook_step.instruction | The step's instruction text |
runbook_step.trigger_tag | The step's trigger tag |
runbook_step.runbook_name | Name of the runbook |
runbook_step.runbook_slug | Slug of the runbook |
EMAIL.* (Addresses)
Available on every event, on both channels. An email notifier addresses its message with these. A webhook can carry them in its payload like any other variable. A variable with nobody behind it renders as nothing.
email.user | The address of whoever user.* names |
email.account | The address of the account an account event is about |
email.case_lead | The lead investigator on the case |
email.case_team | The lead investigator and the investigators, comma separated |
email.message_recipient | The person a notification is addressed to |
email.superusers | Every active superuser, comma separated |
email.tenant_owner | The organization contact address |
DFIRe skips an account that cannot receive: a deactivated one, one with no address, and a service account.
AUTOMATION.* (Automation Rule Triggered)
automation.rule_name | Name of the rule that fired |
automation.trigger_tag | The rule's trigger tag |
automation.trigger_event | The case event that started the rule |
TIMER.* (Compliance Timer Information)
timer.id | Timer ID |
timer.name | Timer name (e.g., "GDPR 72h") |
timer.framework | Compliance framework |
timer.duration_hours | Total duration in hours |
timer.authority_name | Regulatory authority |
timer.threshold_triggered | Which threshold was triggered (50, 25, 10, 0) |
timer.time_remaining_seconds | Seconds remaining until deadline |
timer.deadline | Deadline timestamp |
PLAYBOOK.* (Playbook Review Due)
Available on the Playbook Review Due event only. The reminder carries no case, so the case variables are empty for it.
playbook.id | Playbook ID |
playbook.name | Playbook name |
playbook.description | What the playbook covers |
playbook.creator | The author label, from the playbook's Author field |
playbook.url | Direct URL to the playbook, empty when no base URL is configured |
playbook.owner_names | List of the active users listed as owners. A role listed as an owner is reported as the role, not as the people in it |
playbook.owner_group_names | List of the roles listed as owners. The reminder reaches their members too |
playbook.owner_summary | Both lists on one line, people first, comma separated. Empty when the playbook has no owner |
playbook.owner_status | assigned when either owner list names anything, unassigned when both are empty, inactive when the playbook names users, names no role, and every listed user has been deactivated |
playbook.created_at | Creation date, null when the playbook has none |
playbook.updated_at | When the definition last changed |
playbook.last_reviewed_at | When the playbook was last reviewed, null when never |
playbook.last_reviewed_by_name | Who recorded that review |
playbook.revision | Current revision of the definition |
playbook.last_reviewed_revision | Revision the last review certified |
playbook.changed_since_review | True when the definition changed after the last review |
playbook.never_reviewed | True when no review has ever been recorded |
playbook.review_period_days | The review period in force |
playbook.review_due_at | Review deadline |
playbook.review_status | current, due_soon or stale |
playbook.days_until_stale | Whole days to the deadline, rounded up. It reads 0 from the deadline until a day past it, then negative |
playbook.warning_days | The reminder day that fired. 0 is the deadline itself |
playbook.event_id | Identifier of this reminder, stable across retries |
playbook.triggered_at | When the reminder fired |
DFIRe decides the status on the exact deadline timestamp, not on the day count. A playbook that has just passed its deadline therefore reports days_until_stale of 0 and a status of stale.
owner_names and owner_group_names are lists. A list placed inside quotes in a template arrives as its JSON text. The receiver reads the string ["Ada Lovelace"] rather than a name. Use owner_summary where you want a readable line.
Select the event in the webhook editor. While the payload template is still empty, Insert example fills it with a template built from these variables:
{
"event": "{{meta.event_type}}",
"event_id": "{{playbook.event_id}}",
"playbook": "{{playbook.name}}",
"owners": "{{playbook.owner_summary}}",
"status": "{{playbook.review_status}}",
"due": "{{playbook.review_due_at}}",
"url": "{{playbook.url}}"
}
REPORT.* (Report Finalized)
report.version | The version the report reached. The first finalization sets it to 1.0 |
report.finalized_at | When the report was finalized |
report.finalized_by | Username of the person who finalized it |
report.report_url | Direct URL to the report, empty when no base URL is configured |
BACKUP.* (Backup Finished)
A backup covers the whole installation, so the event carries no case and the case variables are empty for it. The user variables name whoever asked for the backup, and stay empty on a scheduled run.
backup.status | completed or failed |
backup.started_at | When the run started |
backup.finished_at | When the run ended |
backup.size_bytes | Size of the backup file. Empty when the run produced no file |
backup.error | Why the run failed. Empty when it succeeded |
backup.description | The description recorded with the backup. A scheduled run records "Scheduled backup" |
UPDATE.* (DFIRe Update Available)
The daily license check carries the update information, so the event has no case and no acting user.
update.current_version | The version this installation runs |
update.latest_version | The version the license server offers |
update.urgency | available, recommended or critical |
update.release_notes | What the release changes |
SECRET.* (Stored Secrets)
Reference secrets stored in the Secrets tab:
{{secret.<name>}}- Replaced with the secret value at send time
For example, {{secret.pushover_api_token}} inserts the value of a secret named "pushover_api_token".
Value Mappings
Value mappings transform field values before inserting them into the payload. This is useful when the receiving system expects different values than DFIRe uses.
Configuration
Define mappings as a JSON object where keys are field names and values are mapping dictionaries:
{
"severity": {
"info": -2,
"low": -1,
"medium": 0,
"high": 1,
"critical": 2
},
"event_type": {
"MANUAL": "Manual timeline event",
"AUTO": "Automatic system event"
}
}
Usage
Use the |mapped filter to apply mappings:
{{case.severity}}outputs:critical{{case.severity|mapped}}outputs:2(using the mapping above)
This is especially useful for integrating with services like Pushover that use numeric priority levels, or for translating DFIRe field values to match your organization's terminology.
Payload Preview
Click "Preview Payload" to see how your template will render with sample data. The preview shows the actual JSON that would be sent, with variables replaced by example values. This helps verify your template syntax and mappings before saving.
Additional Outgoing Webhook Settings
Retry Configuration
- Max Retries: Number of retry attempts for failed deliveries (default: 3)
- Timeout (Seconds): How long to wait for a response (default: 30)
HMAC Signing
Optionally configure a signing secret to allow the receiving endpoint to verify that requests came from DFIRe:
- If set. DFIRe includes an
X-Webhook-Signatureheader with an HMAC-SHA256 signature - The signature is computed over the request body using your secret
- Leave empty to skip signing
The header value carries a sha256= prefix ahead of the hex digest, for example
X-Webhook-Signature: sha256=9f86d081884c7d65.... Strip the prefix before comparing,
or compare against the prefixed form as the example below does.
Verifying Signatures
# Python example
import hmac
import hashlib
def verify_signature(payload_body, signature_header, secret):
expected = hmac.new(
secret.encode(),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature_header)
Webhook Secrets
Store sensitive values like API keys and tokens securely, then reference them in webhook payloads.
Creating a Secret
- Go to Settings > Webhooks & Email, Webhook secrets tab
- Click Add Secret
- Enter a name (used to reference the secret in templates)
- Enter an optional description
- Enter the secret value
- Click Save
Using Secrets in Payloads
Reference secrets using the syntax {{secret.secret_name}}. The actual value is substituted when the webhook is sent:
{
"token": "{{secret.pushover_token}}",
"user": "{{secret.pushover_user}}",
"message": "Case {{case.case_number}} - {{case.title}}"
}
Security
- Secret values are encrypted at rest using the
CREDENTIAL_ENCRYPTION_KEY - Values are never exposed in API responses or delivery logs
- Only the secret name and description are visible after creation
- You can update or delete secrets, but cannot view the stored value
Example: Pushover Notifications
This example shows how to send mobile push notifications via Pushover when someone adds "@page" to a timeline event.
1. Create Secrets
Store your Pushover credentials in Webhook Secrets:
- Create a secret named
pushover_api_tokenwith your Pushover application token - Create a secret named
pushover_api_userwith your Pushover user key
2. Create the Webhook
- Name: Pushover on Timeline @page event
- Endpoint URL:
https://api.pushover.net/1/messages.json - Trigger Events: Timeline Event Created
- Content Filter:
@page - Authentication: None (credentials are in the payload)
3. Configure the Payload Template
{
"token": "{{secret.pushover_api_token}}",
"user": "{{secret.pushover_api_user}}",
"title": "You have been paged for {{case.case_number}} {{case.title}}",
"message": "You have been paged by {{event.actor_username}} on case {{case.case_number}}:\n\n{{event.event_type|mapped}}: {{event.title}}, {{event.description}}.\n\nYou can access the incident here: {{meta.case_url}}",
"priority": "{{case.severity|mapped}}"
}
4. Configure Value Mappings
{
"severity": {
"info": -2,
"low": -1,
"medium": 0,
"high": 1,
"critical": 2
},
"event_type": {
"MANUAL": "Manual timeline event"
}
}
Now, when anyone adds a timeline event containing "@page", the on-call team receives a push notification with case details and a direct link.
Webhook Management
Testing Webhooks
Use the play button on a webhook card to send a test event. This fires the "Test Event (Manual Testing)" trigger with sample data.
Enabling/Disabling
Toggle the switch on a webhook card to enable or disable it without deleting the configuration. Disabled webhooks are not triggered.
Duplicating Webhooks
Use the copy button to duplicate a webhook configuration. This is useful for creating similar webhooks with minor variations.
Delivery History
Click on a webhook to view recent delivery attempts showing:
- Success or failure status
- HTTP response code
- Response time
- Timestamp
Retry Behavior
Failed deliveries are retried automatically, one minute after the first failure, then five minutes, then every thirty minutes. After the configured number of retries, the delivery is marked as failed but the webhook remains active for future events. A retried payload is rebuilt at retry time, so it reflects the current state of the case rather than its state when the event fired. The Playbook Review Due reminder is the exception. DFIRe captures its payload when the reminder fires and sends that same payload with every retry, even after the playbook has been edited, reviewed or deleted.
Security Best Practices
- Use HTTPS: Always use HTTPS endpoints for webhooks
- Use Secrets: Store API keys and tokens in Webhook Secrets rather than hardcoding in templates
- Enable Signing: Configure a signing secret and verify signatures in your receiving application
- Restrict Access: If possible, whitelist DFIRe's IP address in your receiving application
- Monitor Failures: Review delivery history for failures that might indicate configuration issues