Webhooks
Push instead of poll — signed events at your endpoint when a change is detected or a rule raises an alert. Signing, verification, retries and rotation, in full.
The two events at the heart of the product are the graph's own: change_event.detected is the firehose for your graph, and alert.created is only what your rules decided mattered. Four more cover the things a compliance platform syncs on rather than polls for — an inventory change, a finished artifact, a signed attestation. An endpoint subscribes to any of them, or to *. Endpoints are managed at Settings, Integrations — or over the API.
The event types
The envelope
Every delivery is one JSON object, Stripe-shaped. The data half carries codes and ids, never display strings — your parser keeps working when a ninth language ships; the localized sentence travels alongside, marked with its locale.
{
"id": "evt_8Kq2mYb4Rn0pWc7xTd1sVg",
"type": "alert.created",
"api_version": "2026-09-01",
"created": "2026-08-18T06:12:04Z",
"livemode": true,
"account_id": 204,
"data": { ... }
}id is the event id: every delivery of one event carries it — automatic retries, a replay from Settings, Integrations, and the copies sent to your other endpoints. Deduplicate on it. created is when the event happened, in UTC ISO 8601, like every timestamp in the API. Every id in data is a public id you can hand back to the API — for alert.created, data.rule.id is the rule's rule_... id, the one /api/v1/rules/{public_id} takes.
api_version is a date, not the API's /v1. It is pinned when the endpoint is created and freezes the envelope shape that endpoint receives — an event pushed to you cannot carry a version in its path the way a request you make can. The two version schemes move independently.Headers and verification
Deliveries are signed in the Standard Webhooks style: HMAC-SHA256 over id.timestamp.payload, base64-encoded, prefixed v1,. The delivery id and timestamp are inside the signed string, so neither can be swapped without breaking the signature.
Verify before parsing. The secret is the whsec_... value shown once when the endpoint was created or its secret rotated; the base64 part after the prefix is the key.
# Ruby
require "openssl"
require "base64"
def verified?(secret, id, timestamp, body, header)
key = Base64.decode64(secret.delete_prefix("whsec_"))
mac = OpenSSL::HMAC.digest("SHA256", key, "#{id}.#{timestamp}.#{body}")
expected = "v1,#{Base64.strict_encode64(mac)}"
header.split(" ").any? do |candidate|
OpenSSL.secure_compare(candidate, expected)
end
end// Node
const crypto = require("crypto");
function verified(secret, id, timestamp, body, header) {
const key = Buffer.from(secret.replace("whsec_", ""), "base64");
const mac = crypto.createHmac("sha256", key)
.update(`${id}.${timestamp}.${body}`).digest("base64");
const expected = `v1,${mac}`;
return header.split(" ").some((candidate) =>
candidate.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(candidate), Buffer.from(expected)));
}Retries and endpoint health
Answer 2xx within 15 seconds; anything else is a failure. A failed delivery is retried up to 8 attempts over roughly 28 hours — immediately, then after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, and twice more after 10 hours. An endpoint that fails 15 deliveries in a row, or fails continuously for 5 days, is disabled automatically and the integrations screen says so — a dead endpoint retried forever is a queue of work nobody will receive. Re-enable it (after fixing the receiver) from the settings screen or with PATCH /api/v1/webhook_endpoints/{id} and {"active": true}.
Delivery history is kept for 30 days, attempt by attempt, and any delivery in that window can be replayed from Settings, Integrations. A replay carries the original payload and envelope id under a new webhook-id, so a receiver that deduplicates on the envelope id sees it as the event it already has.
Secret rotation
A planned rotation issues a new secret and keeps the old one signing for 24 hours — deploy the new secret at your leisure inside the window; at least one signature in the header verifies throughout. The new secret is shown once, in the response to the rotation. For a leaked secret, the settings screen offers an immediate regeneration with no overlap.
Test deliveries
The integrations screen's test button sends a synchronous webhook.test event through the real dispatcher — same signing, same headers — and reports whether your endpoint accepted it. It is deliberately not subscribable: it is a delivery-path check, not something that happens in the product. Its envelope id is a freshly generated evt_... id like any other event's, new for every test, so it exercises the same parsing and deduplication path; data carries only a message.
Empiece con un proveedor. Gratis y sin tarjeta.
Busque una empresa, sígala y vea la cadena que hay debajo. Cambie de plan cuando un solo proveedor deje de bastarle.