Overview
You can use Blumira Webhooks to send notifications to your HTTPS endpoints when finding and case events occur. You can use these notifications to trigger SOAR playbooks, create tickets in external systems, feed SIEMS, and drive custom automation.
Key characteristics
The table below explains some of the key characteristics of the Blumira Webhooks feature.
| Characteristic | What it means |
| Outbound only | Blumira sends notifications to your endpoint; you cannot send notifications to Blumira. |
| Per-org configuration | Each webhook belongs to a single Blumira account. |
| HMAC-signed | Blumira signs each delivery, so you can verify that the notification came from Blumira. |
| Filterable | You choose which event types you will receive notifications for. |
| Automatic retries | Blumira retries failed deliveries up to five times over approximately one hour and keeps an audit trail of every delivery attempt. |
| Auto-disable | To prevent noise, Blumira automatically disables webhook endpoints that have sustained failures. |
Supported event types
Blumira delivers notifications for the finding events, case events, and synthetic events detailed below.
| Event type | Fires when |
finding.created |
Blumira creates a new finding. |
finding.status.changed |
A finding's status changes. |
finding.owners.changed |
Someone assigns or reassigns a finding. |
finding.comment.added |
Someone adds a comment to a finding. |
case.created |
Blumira creates a new case. |
case.status.changed |
A case's status changes. |
case.severity.changed |
A case's severity level changes. |
webhook.test |
You trigger a test delivery from the UI or API. This is not a real event. |
webhook.secret.rotated |
Your endpoint's HMAC secret is rotated. During the overlap window, Blumira dual-signs this event. |
Getting started
Endpoint URL requirements
When you create your endpoint, ensure the following:
- You use HTTPS only. Blumira rejects HTTP, file://, and custom schemes.
- The endpoint always resolves to a public IP. Blumira blocks private IPs (RFC 1918), loopback, link-local, metadata endpoints (169.254.169.254), and CGNAT ranges.
Creating a webhook
To create a webhook, do the following:
-
In Blumira, navigate to Settings > Webhooks or to MSP Portal > Webhooks if you are an MSP managing customer sub-account data.
Reference: See Using Blumira Webhooks as an MSP. - Click Add Webhook.
- Type your Endpoint URL.
- (Optional) Add a description.
-
(Optional) Under Custom headers, add a header by typing a Header name and Value. See more at HTTP headers below.
Note: To add additional headers, click + Add header, and type the Header name and Value for each header you want to add. - (Optional) Under Event filters, click + Add filter, and select the Event type and finding type, priorities, or case severity beneath that type, depending on the events you want to filter for. See more at Filtering webhook events below.
- Click Create webhook.
-
Copy and save the HMAC signing secret.
Important: You must copy the secret before you close the window. You will not be able to see the secret again, and if you lose it, you must rotate to get a new one. - Click Done.
Sending a test delivery
To test your webhook, do the following:
- In the Webhooks table, locate the endpoint URL you want to test.
- Click the ellipsis at the end of the row.
- In the context menu, click Send Test.
Example: See the code below for an example of a test payload.
{
"id": "evt_ddee1122-ccdd-4eef-8899-001122334455",
"type": "webhook.test",
"schema_version": "1",
"livemode": false,
"event_created_at": "2026-05-13T14:22:31.123456Z",
"delivery_created_at": "2026-05-13T14:22:33.654321Z",
"org_id": "0a1b2c3d-1111-2222-3333-444455556666",
"msp_org_id": null,
"data": {
"endpoint_id": "aa11bb22-cc33-dd44-ee55-ff6677889900",
"message": "Test delivery from Blumira webhook-integration. If you can read this, your signature verification and handler are working.",
"triggered_by": {
"kind": "customer_user",
"person_id": "11111111-2222-3333-4444-555555555555",
"email": "responder@customer.example.com",
"name": "Jane Responder"
}
}
}
Understanding payload format
Each delivery to your endpoint contains crucial information including the event type, when the event was created, and who or what triggered the event. You can find details about the data Blumira includes in each section of the delivery below.
JSON envelope
Every webhook delivery uses a consistent JSON envelope.
Example:
{
"schema_version": "1",
"id": "evt_a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"type": "case.status.changed",
"livemode": true,
"event_created_at": "2026-05-13T14:22:31Z",
"delivery_created_at": "2026-05-13T14:22:33Z",
"org_id": "0a1b2c3d-1111-2222-3333-444455556666",
"msp_org_id": null,
"data": { }
}
See the table below for a description of each field.
| Field | Type | Description |
schema_version |
string | Always "1". Blumira increments this value on breaking changes. |
id |
string | Idempotency key. Format: evt_<UUID>. Use this for deduplication. Stable across retries. |
type |
string | Event type discriminator (e.g., finding.created). |
livemode |
boolean |
true for real production events. false only for webhook.test. |
event_created_at |
string (ISO 8601) | When the source event occurred upstream. Stable across retries. |
delivery_created_at |
string (ISO 8601) | When Blumira enqueued the delivery for your endpoint. |
org_id |
string (UUID) | The child organization that produced the event. |
msp_org_id |
string (UUID) or null
|
The MSP parent org ID, if the notification is delivered from an MSP-tier webhook. Always present as a key;null when coming from single-org webhooks. |
data |
object | Event-type-specific payload. See different payloads by event type below. |
Envelope timestamps (i.e., event_created_at, delivery_created_at) include six fractional digits when the source has sub-second precision and omit fractional digits when the source does not.
Timestamps inside data (e.g., created_at, matched, expires_at) come from the original source and might use different precision. Always use a standard ISO 8601 or RFC 3339 parser. Do not rely on a fixed number of fractional digits.
HTTP headers
Every delivery includes the following headers:
Content-TypeUser-AgentX-Blumira-SignatureX-Blumira-Event-TypeX-Blumira-Delivery-IdX-Blumira-Delivery-Attempt
If you configured custom headers or an authorization header, the delivery includes those headers as well. See more about custom headers and authorization headers below.
Example:
Content-Type: application/json
User-Agent: Blumira-Webhooks/1.0
X-Blumira-Signature: t=1715608951,v1=<hex_hmac_sha256>
X-Blumira-Event-Type: case.status.changed
X-Blumira-Delivery-Id: 0a1b2c3d-4e5f-4789-9bcd-ef0123456789
X-Blumira-Delivery-Attempt: 1
X-Blumira-Delivery-Attempt increments from 1 to 5 as Blumira retries failed deliveries.
Org_id and msp_org_id are not available as HTTP headers. If you want to route by organization without parsing the body, use the X-Blumira-Event-Type header for event-level routing.
Event type data block
The data object varies by event type. The following sections illustrate the shape for each supported event:
- Finding created
- Finding status changed
- Finding owners changed
- Finding comment added
- Case created
- Case status changed
- Case severity changed
Many events include an actor field that identifies who or what triggered the event. See the table below for a description of each actor.
| Actor | Shape | Description |
customer_user |
{"kind": "customer_user", "person_id": "...", "email": "...", "name": "..."} |
A user in your organization. |
blumira_analyst |
{"kind": "blumira_analyst", "person_id": "..."} |
A Blumira analyst. Email and name are never included. |
blumira_system |
{"kind": "blumira_system"} |
An automated Blumira process. |
Finding created
{
"finding_id": "ffffffff-1111-2555-0000-000000000001",
"finding_short_id": "F-26-21-A1B2",
"finding_web_url": "https://app.blumira.com/.../findings/...",
"name": "Suspect: AD Domain Admin Created",
"summary": "An account was promoted to Domain Admin from an unusual source IP.",
"priority": 2,
"priority_label": "P2",
"status": "open",
"type": "suspect",
"category": "privilege_escalation",
"owners": {
"analysts": [{"kind": "blumira_analyst", "person_id": "..."}],
"responders": [{"kind": "customer_user", "person_id": "...", "email": "...", "name": "..."}],
"managers": []
},
"actor": {"kind": "blumira_system"},
"src_country": ["US"],
"matched": "2026-05-13T14:20:00.000000Z",
"created_at": "2026-05-13T14:22:31.000000Z"
}Finding status changed
{
"finding_id": "...",
"finding_short_id": "F-26-21-A1B2",
"finding_web_url": "https://app.blumira.com/.../findings/...",
"name": "Suspect: AD Domain Admin Created",
"status_change": {"previous": "open", "current": "resolved"},
"priority": 2,
"priority_label": "P2",
"type": "suspect",
"category": "privilege_escalation",
"resolution": "false_positive",
"resolution_notes": "Confirmed-noise rule was misconfigured; updated.",
"actor": {"kind": "blumira_analyst", "person_id": "..."},
"created_at": "2026-05-13T15:02:10.000000Z"
}Finding owners changed
{
"finding_id": "...",
"finding_short_id": "F-26-21-A1B2",
"finding_web_url": "https://app.blumira.com/.../findings/...",
"name": "Suspect: AD Domain Admin Created",
"status": "open",
"priority": 2,
"priority_label": "P2",
"type": "suspect",
"category": "privilege_escalation",
"owners_change": {
"analysts": {
"added": [{"kind": "blumira_analyst", "person_id": "..."}],
"removed": []
},
"responders": {
"added": [],
"removed": [{"kind": "customer_user", "person_id": "...", "email": "old-responder@example.com", "name": "Olivia Old"}]
},
"managers": {"added": [], "removed": []}
},
"actor": {"kind": "customer_user", "person_id": "...", "email": "admin@example.com", "name": "Alex Admin"},
"created_at": "2026-05-13T15:10:00.000000Z"
}Finding comment added
{
"finding_id": "...",
"finding_short_id": "F-26-21-A1B2",
"finding_web_url": "https://app.blumira.com/.../findings/...",
"type": "suspect",
"category": "privilege_escalation",
"comment_id": "bbbbbbbb-0000-0000-0000-0000000000e1",
"comment_created_at": "2026-05-13T15:20:00.000000Z",
"actor": {"kind": "blumira_analyst", "person_id": "..."},
"comment_body": "Confirmed false positive — closing this out.",
"comment_body_truncated": false
}Blumira caps the comment_body field at 64 KiB (65,536 bytes of UTF-8). If the comment exceeds the cap, the body is truncated at a code-point boundary and comment_body_truncated is true. Otherwise, comment_body_truncated is false.
Case created
{
"case_id": "550e8400-e29b-41d4-a716-446655440000",
"case_web_url": "https://torch.blumira.com/cases/case?id=550e8400-...",
"title": "Suspicious admin account creation",
"status": "new",
"severity": "high",
"created_at": "2026-05-13T14:22:31.000000Z",
"expires_at": "2026-05-20T14:22:31.000000Z",
"customer_summary": "Active Directory admin role assigned to a recently-created account.",
"detection_names": ["AD: Domain Admin Created"],
"findings": [{"finding_id": "...", "detection_name": "AD: Domain Admin Created"}],
"findings_total_count": 1,
"resolution": null,
"resolution_notes": null,
"status_changed_at": null,
"severity_changed_at": null,
"actor": {"kind": "blumira_system"},
"owners": []
}Expires_at shows when the case expires per Blumira's current expiry policy. This field is only meaningful when the case status is active (i.e., new or investigating). Use the expires_at value we provide. Do not compute expiry from created_at.
Blumira caps findings at 10 items. Findings_total_count is the full uncapped count. Use findings_total_count for routing rules such as "escalate when a case has 5 or more findings."
Case status changed
{
"case_id": "550e8400-e29b-41d4-a716-446655440000",
"case_web_url": "https://torch.blumira.com/cases/case?id=550e8400-...",
"title": "Suspicious admin account creation",
"status": "investigating",
"severity": "high",
"status_change": {"previous": "new", "current": "investigating"},
"created_at": "2026-05-13T14:22:31.000000Z",
"expires_at": "2026-05-20T14:22:31.000000Z",
"customer_summary": "...",
"detection_names": ["AD: Domain Admin Created"],
"findings": [{"finding_id": "...", "detection_name": "AD: Domain Admin Created"}],
"findings_total_count": 1,
"resolution": null,
"resolution_notes": null,
"status_changed_at": "2026-05-13T15:10:00.000000Z",
"severity_changed_at": null,
"actor": {"kind": "customer_user", "person_id": "...", "email": "responder@example.com", "name": "Jane Responder"},
"owners": []
}Resolution and resolution_notes are null until the status transitions to a resolved state.
Case severity changed
{
"case_id": "550e8400-e29b-41d4-a716-446655440000",
"case_web_url": "https://torch.blumira.com/cases/case?id=550e8400-...",
"title": "Suspicious admin account creation",
"status": "investigating",
"severity": "high",
"severity_change": {"previous": "low", "current": "high", "direction": "escalated"},
"created_at": "2026-05-13T14:22:31.000000Z",
"expires_at": "2026-05-20T14:22:31.000000Z",
"customer_summary": "...",
"detection_names": ["AD: Domain Admin Created"],
"findings": [{"finding_id": "...", "detection_name": "AD: Domain Admin Created"}],
"findings_total_count": 1,
"resolution": null,
"resolution_notes": null,
"status_changed_at": "2026-05-13T15:10:00.000000Z",
"severity_changed_at": "2026-05-14T09:30:00.000000Z",
"actor": {"kind": "blumira_system"},
"owners": []
}If a case's severity changes after it is closed or expired, Blumira will not deliver a case.severity.changed event. The severity on other case events (e.g., case.status.changed) will still show the current value.
Finding_web_url and case_web_url are browser links, not API endpoints. These links point to the Blumira web UI and return HTML for browser navigation, not JSON. To fetch current resource state programmatically, use the Blumira public API endpoints constructed from the finding_id or case_id in the payload.
Filtering webhook events
By default, Blumira sends all events to your webhook endpoint. Add webhook event filters to narrow delivery to specific event types, priorities, and severities. See the table below for what each filter option specifies.
| Field | Applies To | Description |
event_type |
All | Required. Select the event type you want to filter for (e.g., finding.created). |
finding_types |
Finding events only |
Optional array. Filter by finding type: If this field is empty, you will receive all finding types for the event you selected. |
priorities |
Finding events only |
Optional array. Filter by finding priority: If this field is empty, you will receive all priorities for the type you selected. |
severities |
Case events only |
Optional array. Filter by case severity: If this field is empty, you will receive all severities for the event you selected. |
Filter logic
Filters function in the following ways:
- An endpoint with no filters receives all events (every event type, no dimension narrowing). This is the default for a newly created webhook with no filters added.
- When a webhook has one or more filters, subscription becomes per-event-type. The endpoint receives only the event types for which it has a filter row. Adding your first filter narrows the events Blumira delivers to the endpoint from "all events" to "only these."
- Within a single filter row, the dimension arrays (e.g.,
finding_types,priorities,severities) are conjunctive, so an event must match all specified dimensions to pass to the endpoint. An empty or omitted dimension array means "all values" for that dimension. - Multiple filter rows for different event types are disjunctive, so an event matching any filter passes.
- You can remove all filters and return an endpoint to "all events" by sending
filters: []on update.
Security
HMAC signatures
Every delivery includes a message ({timestamp}.{raw_request_body}) that is signed with your webhook's HMAC signature. The signature’s format is:
t=<unix_timestamp>,v1=<hex_hmac_sha256_digest>The signature is in the X-Blumira-Signature header.
Example:
X-Blumira-Signature: t=1715608951,v1=a1b2c3d4e5f6...
Verifying signatures with code
Use the following to verify HMAC signatures, depending on your preferred language:
| Language | Code |
|---|---|
| Python |
|
| Node.js | |
| Go | |
Replay protection
The t= timestamp in the signature header is the wall-clock time when Blumira signed the delivery. This delivery timestamp changes on every retry attempt, unlike event_created_at, which is stable.
We recommend that you reject signatures older than five minutes (|now - t| > 300 seconds). This prevents captured payloads from being replayed against your endpoint later.
Rotating the secret
You can rotate your endpoint’s HMAC signing secret any time. To rotate your endpoint's secret, do the following:
- Use the UI method.
- Blumira generates a new 32-byte secret and returns it once in the response. Copy and store the HMAC signing secret immediately.
A dual-sign overlap window opens (default is 24 hours, configurable up to 7 days). Blumira sends a webhook.secret.rotated synthetic event to your endpoint. During the overlap window, the event is dual-signed. To verify that your handler has switched to the new secret, send a test delivery after you deploy the new key.
Dual-sign overlap window
During the overlap window, every delivery includes two v1= signatures in the X-Blumira-Signature header, one computed with the new secret, one with the previous secret.
Example:
X-Blumira-Signature: t=1715608951,v1=<new_sig>,v1=<old_sig>
Your verification code, which loops over v1= values and accepts on any match (as shown in the example above), handles rotation transparently. You do not need to change the code.
You can use the following to verify which secret matched, which will indicate whether your handler is still using the old key:
| Language | Code |
|---|---|
| Python |
|
| Node.js |
|
| Go | |
verifyWebhook function when you need to verify a single secret. Use one of the dual-verify methods above if you need to log which secret matched or gate deployment automation until you confirm the new secret is active.After the overlap window expires, Blumira will only use the new secret for signing.
Rotation acknowledgment
When you successfully receive and verify the webhook.secret.rotated delivery, Blumira records the acknowledgement. If the synthetic delivery fails, ensure that you deployed the new secret. If you deployed the secret, you can trigger acknowledgment manually by sending a test delivery.
Custom headers and authorization headers
You can configure additional headers to send with every delivery:
- Custom headers: Arbitrary key-value pairs (e.g.,
X-API-Key: your-api-key). Blumira stores header values in GCP Secret Manager and never exposes them via the API. Only header names appear on GET responses. - Auth header: An optional
Authorizationheader value, which is useful if your endpoint requires bearer token authentication.
remove_custom_headers field.Delivery and retry behavior
Delivery statuses
See the table below for information about different delivery statuses.
| Status | Meaning |
pending |
Queued for delivery (or scheduled for retry). |
in_flight |
Currently being dispatched. |
succeeded |
Your endpoint returned 2xx. |
failed |
Permanently failed (non-retryable error, e.g., 4xx other than 408 or 429). |
dead_lettered |
All 5 retry attempts were exhausted without success. |
Retry schedule
Retryable conditions include timeouts, connection errors, DNS failures, TLS errors, HTTP 408, 429, and 5xx responses. Non-retryable conditions include HTTP 3xx (Blumira does not follow redirects) and 4xx responses other than 408 or 429 (these indicate a permanent configuration issue on your side).
If your endpoint returns a non-2xx response or the connection fails, Blumira retries the delivery according to this schedule:
| Attempt | Wait after previous attempt |
| 1 | Immediate |
| 2 | +30 seconds |
| 3 | +2 minutes |
| 4 | +10 minutes |
| 5 | +45 minutes |
| — | Dead-lettered |
The waits are cumulative, so the fifth and final attempt is approximately 1 hour after the first. After 5 failed attempts, the delivery is dead-lettered, meaning it has permanently failed for this endpoint.
Auto-disable
Blumira automatically disables endpoints that experience sustained delivery failures. This protects both your systems and the delivery pipeline. We auto-disable endpoints when any of the following conditions are met:
- 20 consecutive failures. The failure counter increments by 1 per failure and heals by 2 per success, so transient issues recover naturally. Only sustained failure reaches the threshold.
- 80% failure rate over 1 hour (minimum 10 delivery attempts in the window).
- 100% failure rate over 24 hours (minimum 3 delivery attempts in the window).
When Blumira auto-disables an endpoint, you receive an email notification with the reason. The endpoint stops receiving new deliveries until you re-enable it.
webhook.test) and rotation synthetics (webhook.secret.rotated) do not affect auto-disable counters. Test deliveries bypass all aggregate health tracking.Re-enabling a disabled endpoint
To re-enable a disabled endpoint, do the following:
- Verify and fix the issue with your endpoint.
- Navigate to Settings > Webhooks.
- In the webhook’s row, click the ellipsis.
- In the context menu, click Edit.
- In the Edit webhook window, click the slider next to Enabled.
The endpoint enters a verification window called probation; it must successfully receive 3 consecutive real-event deliveries to fully clear probation. During probation, a single terminal failure (non-2xx on a real event) immediately disables the endpoint again.
Idempotency
Deduplication
The envelope id field (evt_<UUID>) is the idempotency key. Always deduplicate on id. The same idis stable across all retry attempts of a single delivery and appears on deliveries to different endpoints for the same source event (cross-endpoint fan-out).
Your handler should maintain a seen-IDs cache and skip processing if the id has already been handled. A 24-hour cache window is typically sufficient.
Cross-endpoint fan-out
When a single source event matches multiple endpoints (for example, both an MSP-tier and a single-org endpoint), each endpoint receives its own delivery with the following:
- The same envelope
id - Different
X-Blumira-Delivery-Idvalues - Different HMAC signatures (each endpoint has its own secret)
This is intentional fan-out. If you want cross-endpoint deduplication, hash by id and keep whichever delivery arrived first.
Ordering and staleness
Deliveries do not always arrive in source-event order. Retries, multi-pod processing, and per-endpoint retry chains can cause a later event to arrive sooner than an earlier event.
To keep your data organized, we recommend that you do the following:
- Order by
event_created_at, not arrival order. This is the upstream timestamp of when the event actually occurred. - Deduplicate on
idto handle retries delivering the same event multiple times. - Always re-fetch the current state via the Blumira API if your automation needs the latest state. Webhook payloads are frozen at their enqueue time, so they reflect the state when the event occurred, not the current state.
Example: See below for safe state-machine handling.
def on_webhook(event):
last_seen = state.get(event["data"]["case_id"], "1970-01-01T00:00:00Z")
if event["event_created_at"] <= last_seen:
return # older event arrived late; skip
state[event["data"]["case_id"]] = event["event_created_at"]
process(event)
Delivery history
You can inspect delivery history for an endpoint using Blumira's UI. Navigate to the webhook’s detail page to see a list of recent deliveries with status, event type, attempt count, and timestamps.
You can expand each delivery to show individual attempts with response codes and timing.
Quotas and limits
See the table below for quota and limit information.
| Resource | Limit |
| Endpoints per organization | 20 |
| MSP-tier endpoints per MSP parent | 20 |
| Filters per endpoint | 10 |
| Custom headers per endpoint | 16 |
| Test deliveries per endpoint | 10/hour |
Troubleshooting
Endpoint is not receiving events
If your endpoint is not receiving events, do the following:
- Verify that the endpoint is enabled. Blumira might have auto-disabled it.
- Navigate to Settings > Webhooks (or MSP Portal > Webhooks for MSP-tier endpoints), and check the endpoint status.
- Verify your filters. If you have filters configured, Blumira only delivers an event if it matches a filter. Verify that you have a filter row for each event type and the matching dimensions you want delivered. An endpoint with no filters receives all events. If you expected to be filtering and are not, you might have removed all filters.
-
Verify that your URL is reachable.
Note: You must use HTTPS and a public IP. Blumira rejects private and internal URLs. - Send a test delivery to verify connectivity and signature verification.
Deliveries are failing with 4xx errors
See the meaning of your error code, and follow the suggested action:
- 403 or 401: Your endpoint is rejecting the request. Verify that any auth tokens configured as custom headers are correct.
- 400: Your endpoint is rejecting the payload. Verify that your handler accepts
POSTwithContent-Type: application/json.
Signature verification fails
If your signature verification fails, do the following:
- Verify that you are using the raw request body bytes and not parsed-then-re-serialized JSON.
- Verify that your secret is the correct current secret (or previous secret if within the dual-sign window).
- If you use replay protection, verify that your server clock is within tolerance.
Endpoint is auto-disabled
See the explanations of each auto_disable_reason field below:
-
consecutive_failures: 20 or more consecutive failed deliveries. -
failure_rate_1h: 80% or higher failure rate over 1 hour. -
failure_rate_24h: 100% failure rate over 24 hours.
Fix the underlying issue, and then re-enable the endpoint. The endpoint enters a 3-delivery verification window.