Delivery & retries
From the Webhooks API reference. Requests use the base URL https://app.warmerly.com/api/v1 and an API key, see Authentication.
Deliveries are dispatched by a background sweep (not real-time) that polls for new events every 5 minutes. Each delivery is a POST to your targetUrl, with content-type: application/json and the X-Warmerly-Signature header:
{
"id": "9d1e0b7a-5e7b-4f10-b2aa-1c8f0d3e6a4b",
"type": "score_drop",
"accountId": "3a6f9e2e-4b7a-4e9a-9b1d-6e3a2f8c1a10",
"occurredAt": "2026-08-18T09:00:00.000Z",
"data": { "severity": "warning", "title": "Health score dropped", "body": "...", "scoreBefore": 88, "scoreAfter": 61 }
}id is the ID of the underlying event and occurredAt is when it was recorded. data's shape varies by type. For score_drop, score_floor, oauth_disconnected, auth_fail and dns_changed it is { severity, title, body, scoreBefore, scoreAfter } (severity is info, warning or critical; the score fields can be null). For the other event types it is the stored event payload as-is, which is not separately documented per type (see Known gaps).
- Each delivery is tried up to 3 times (0.5 s, then 1 s, between attempts). An attempt fails if your endpoint doesn't respond
2xxwithin 5 seconds, or errors. - Events are delivered in order. An event that fails all 3 attempts is not redelivered in a later sweep: the subscription moves on past it.
- A subscription is automatically disabled (
active: false) after 10 consecutive sweeps in which a delivery failed. Re-enable it withPATCH .../{id}. - Up to 50 events per subscription are delivered per sweep; if you have a burst larger than that, the remainder is delivered on the next sweep.
- Redirects are never followed; a
3xxresponse counts as a failure, so respond2xxdirectly fromtargetUrl. lastDeliveryStatusissuccessorfailed. On failurelastDeliveryErroris one ofhttp_<status>,timeout,connection_failed,unsafe_target_urlordns_resolution_failed.
#Verifying deliveries
Every delivery carries an X-Warmerly-Signature header: the hex-encoded HMAC-SHA256 of the raw request body, signed with your subscription's secret.
X-Warmerly-Signature: 3f9a7c2e... (hex-encoded HMAC-SHA256 of the raw body)Verify it by recomputing the HMAC over the exact bytes of the received body and comparing:
const crypto = require("crypto");
const expected = crypto.createHmac("sha256", subscriptionSecret).update(rawBody).digest("hex");
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSignature));