WarnFireDocs

API automation

Lifecycle webhooks

Receive signed, ordered incident lifecycle events at your own HTTPS endpoint, and build a receiver that stays correct through retries, duplicates, and skips.

Version 1.0 Status Preview For Integration developersFor Tenant administratorsFor Tenant owners webhooksautomationidempotencysecurity

WarnFire can send each incident lifecycle transition to HTTPS endpoints you configure. Every message is signed, every message for one incident is numbered without gaps, and WarnFire delivers them to one endpoint strictly in order: the next message for an incident is not sent until you have accepted the one before it.

This page is the receiver contract. The event shapes are also in the API reference .

What is sent

Event typeSent when
incident.triggeredA new incident opens.
incident.acknowledgedA responder acknowledges it.
incident.severity_changedThe source re-triggers the open incident at another severity.
incident.resolvedThe source resolves it, or an operator force-resolves it (details.forced).
incident.assistance_respondedA responder accepts, delays, or declines an assistance request.
incident.escalation_ended_unacknowledgedEscalation ended without an acknowledgement. details carries the recorded paging evidence and paging_outcome: confirmed_accepted (at least one provider accepted a page for handoff — not proof that anyone received it), unconfirmed (a provider never reported back), or not_paged (no reachable destination).
webhook.sequence_skippedA workspace administrator skipped a delivery WarnFire never saw you accept. Its fields sit directly under data, not under details. See Dead deliveries, replay, and skip .

Each endpoint subscribes to the event types it wants. An event type an endpoint is not subscribed to is not captured for it and does not take a sequence number on that endpoint.

Bodies never contain a person’s name, a phone number, an email address, a device, or the alert’s own content. They carry identifiers and state, so a receiver looks up anything else it needs through an authenticated API call.

Message format

Requests are POST with Content-Type: application/json and the Standard Webhooks headers:

HeaderMeaning
webhook-idThe message id. It is the same on every retry and replay of the same message and is never reused for different bytes.
webhook-timestampUnix seconds when this attempt was sent.
webhook-signatureOne or more v1,<base64> entries separated by spaces.

The signature is HMAC-SHA256 with your endpoint’s secret over the string webhook-id, a period, webhook-timestamp, a period, and the raw request body. The secret is whsec_ followed by base64; decode the base64 part to get the key. Accept the message when any entry verifies against any secret you hold, and reject a timestamp more than five minutes from your clock. Compute the HMAC over the bytes you received, before parsing.

The body is frozen when the event is captured. Retries and replays send the same bytes, and a later transition is never reflected in an earlier message: an incident.acknowledged body does not know the incident was later resolved.

{
  "type": "incident.acknowledged",
  "timestamp": "2026-09-28T17:04:11.482910Z",
  "data": {
    "event_id": "evt_918273",
    "message_id": "msg_5f0c…",
    "sequence": 2,
    "incident_sequence": 3,
    "first_for_endpoint": false,
    "incident": {
      "id": "3d8f…",
      "status": "acknowledged",
      "severity": "critical",
      "current_severity": "warning",
      "service": { "id": "9b21…", "name": "Checkout" },
      "created_at": "2026-09-28T17:01:40.117222Z",
      "acked_at": "2026-09-28T17:04:11.482910Z"
    },
    "details": {}
  }
}
  • sequence numbers this endpoint’s messages for this incident: 1, 2, 3, … with no gaps. It is the field a receiver orders on.
  • incident_sequence numbers every captured transition of the incident across all endpoints. Two endpoints with different subscriptions see the same incident_sequence values but different sequence values.
  • first_for_endpoint is true on the first message this endpoint receives for the incident, which may not be incident.triggered if the endpoint was added or subscribed later.
  • event_id identifies the transition itself and is the same across endpoints; message_id equals the webhook-id header.
  • incident.severity is the severity the incident was routed on and does not change. incident.current_severity follows incident.severity_changed.
  • details depends on the type: {"from","to"} for a severity change, {"forced"} for a resolution, {"assistance_request_id","outcome"} for an assistance response, and the paging evidence counts for an escalation that ended unacknowledged.

Payloads for each subscription

Every checked incident event uses the complete envelope above. The type, data.incident.status, and data.details values vary as follows. The examples below are the complete data.details objects; all other fields, including event_id, message_id, both sequence numbers, and the incident snapshot, are present on every event. An endpoint first subscribed after an incident opened may receive any of these as its first event, with sequence: 1 and first_for_endpoint: true.

typedata.incident.statusdata.details
incident.triggeredtriggered{}
incident.acknowledgedacknowledged{}
incident.severity_changedCurrent incident status, often triggered{"from":"warning","to":"critical"}
incident.resolvedresolved{"forced":false} for a source resolution; {"forced":true} for an operator force-resolution.
incident.assistance_respondedCurrent incident status{"assistance_request_id":"<request UUID>","outcome":"accepted"}; outcome is accepted, delayed, or declined.
incident.escalation_ended_unacknowledgedCurrent incident statusThe paging evidence object below.

incident.severity_changed changes data.incident.current_severity to the to value, while data.incident.severity retains the original routing severity. A null from or to value is possible if a timeline transition lacks that field; receivers should treat the fields as nullable strings.

An unacknowledged escalation carries these exact keys and types:

{
  "terminal_status": "known_failed",
  "stop_reason": "ladder_exhausted",
  "accepted_destinations": 0,
  "unknown_destinations": 0,
  "known_failed_destinations": 1,
  "no_destination_count": 0,
  "skipped_destinations": 0,
  "paging_outcome": "not_paged"
}

The counts are recorded attempt evidence, not a delivery or human-read guarantee. terminal_status and stop_reason are recorded reason strings; terminal_status may be null and stop_reason may be empty if the originating timeline entry did not supply them. paging_outcome is confirmed_accepted when at least one destination was accepted by a provider, unconfirmed when none was accepted but at least one has an unknown provider result, and not_paged otherwise.

Receiver contract

Follow these rules and your receiver stays correct through every retry, duplicate, and skip WarnFire can produce.

  1. Verify before anything else. Reject a bad signature or a stale timestamp with a non-2xx status.
  2. Answer 2xx only once the event is durably recorded. Receipt is not acceptance. Write to your database or queue first, then respond. Anything other than a 2xx — including a timeout or a dropped connection — is treated as not accepted, and WarnFire sends the message again.
  3. Be idempotent by webhook-id. If you crash after recording an event but before responding, WarnFire never saw your 2xx and sends the same message again. Recognise the id, do nothing, and answer 2xx.
  4. Acknowledge anything that does not advance the sequence. Keep the highest sequence you have applied per endpoint and incident — sequence is numbered per endpoint, so a receiver that serves more than one endpoint must not share one counter between them. A message at or below that mark is old news: answer 2xx and take no action. Never reject a duplicate.
  5. Treat a skip tombstone as a slot you may already have. A webhook.sequence_skipped message reuses the skipped message’s sequence with its own webhook-id. The original may well have reached you: a skip happens when WarnFire never saw your 2xx, which includes a response that was lost after you recorded the event. So the original and its tombstone can both legitimately receive 2xx from you. If you had applied that sequence, the tombstone is a no-op; if not, record that the slot was skipped. Either way answer 2xx, because the next sequence is held until you do.
  6. Respond within ten seconds. WarnFire’s connection timeout is ten seconds. Do slow work after you have recorded the event, not before you respond.

These rules give you at-least-once, in-order delivery with a stable id per message. Exactly one downstream action per transition is then your side of the bargain: record the webhook-id and the action in the same transaction, or make the action itself idempotent, so a crash between the two cannot repeat it. The rules alone do not guarantee it.

Ordering and delivery

  • Delivery is serialized per endpoint and incident. Message n+1 is sent only after you accept message n. Messages for different incidents, and for different endpoints, are independent and may arrive in any interleaving.
  • A failed attempt is retried with backoff for about 24 hours from the first attempt, starting at 30 seconds between attempts and growing to at most four hours.
  • A message that is still failing after 24 hours is dead. It stays where it is, and everything after it for that incident on that endpoint waits.
  • If every attempt to an endpoint has failed for three days, WarnFire disables the endpoint and emails the workspace owners. Events that occur while an endpoint is disabled are not captured for it and are not sent later.

Dead deliveries, replay, and skip

A dead delivery blocks its incident’s stream until a workspace owner or administrator acts on it in the console, under Configuration → Webhooks.

  • Replay sends the same message — same webhook-id, same bytes — with a fresh 24-hour retry window.
  • Skip gives up on the message and sends a webhook.sequence_skipped tombstone in its slot instead. The tombstone has its own webhook-id. Its data holds sequence, message_id, incident_id, reason (skipped_by_tenant), and the skipped message’s skipped_event_id, skipped_message_id, and skipped_event_type; there is no incident snapshot and no details. The next message is held until you accept the tombstone, so the stream never has a gap: every sequence number is accepted at least once, as the event, its tombstone, or — when a response was lost — both.

Re-enabling a disabled endpoint does not release its dead deliveries; replay or skip them as well.

Dead deliveries also appear in Delivery Activity , alongside failed pages, so a team watching that view sees them without opening the Webhooks page.

Endpoints and secrets

Endpoints are managed in the console under Configuration → Webhooks by workspace owners and administrators (see roles and tenant isolation ). A workspace can have up to ten endpoints.

  • The URL must be https:// and resolve to a public address. Private, loopback, link-local, and cloud-metadata addresses are refused, both when the endpoint is saved and again at connection time, so a DNS answer that changes later is refused too. Redirects are not followed.
  • The signing secret is shown once, when the endpoint is created or its secret is rotated. Store it in your secret manager; WarnFire cannot show it again.
  • Rotation installs a new secret and keeps the previous one signing for 24 hours. During that overlap each message carries two signatures, so a receiver holding either secret verifies. Deploy the new secret to your receiver within the day.
  • Send test delivers a signed test message outside the incident stream, with the endpoint’s credential if it has one, so you can check verification before an incident happens. It works on a disabled endpoint too, so you can confirm a fixed receiver before you re-enable it. A disabled endpoint still receives no incident events until you enable it. Every test is kept in the endpoint’s delivery history for 30 days as a Test row, with its HTTP status and error. Filter by tests only or failed tests. A test is never retried, replayed or skipped, and never holds back incident events.

Receiver authentication

If your receiver’s front door needs a credential before it looks at a body, give the endpoint one. It is sent as an Authorization header on every delivery, retry, replay, and test message, in addition to the signature — never instead of it. Verify the signature regardless.

ModeYou enterWarnFire sends
NoneNothingNo Authorization header
Bearer tokenA tokenAuthorization: Bearer <token>
Basic authA username and passwordAuthorization: Basic <base64(username:password)>
  • A token is 1–4096 characters of A–Z a–z 0–9 - . _ ~ + / with optional trailing =; a username is up to 256 printable characters without :; a password is up to 4096 printable characters.
  • The token or password is encrypted at rest with the same key as the signing secret and is never shown again — not in the console, the API, the audit log, or delivery history. Reads report only the mode and, for Basic, the username.
  • To change it, enter a new token or password; leaving the field empty keeps the saved one. Switching modes requires the full new credential. Switching to None removes the saved credential.
  • Deliveries use the credential the endpoint has now, so fixing a wrong token repairs retries already waiting, without a replay.
  • Redirects are not followed, so a credential is only ever sent to the URL you configured.

If a secret is compromised

Two emergency controls are available on the endpoint:

  • Revoke previous secret ends a rotation overlap immediately, for a leaked old secret.
  • Revoke secret replaces the current secret at once, with no overlap, for a leaked current secret; a previous secret still in overlap is revoked as well. The new secret is shown once.

In both cases, remove the revoked secret from your receiver as well. WarnFire no longer signing with a key does not stop an attacker who holds it from forging messages that your receiver would still verify. Revocation is complete only when neither side trusts the key.

History and visibility

The Webhooks page keeps 30 days of delivery history per endpoint, with each attempt’s status, response code, and error, and shows an endpoint’s failure streak, dead deliveries, and disablement reason. Endpoint creation, changes, rotation, revocation, disablement, replay, and skip are recorded in the workspace audit log.