docs · for developers
Webhooks
Push notifications for the things your systems care about: a finished scan, a new tracker, a published version, a completed proof export. Signed, retried, and inspectable.
Endpoints
Create endpoints under Workspace settings → Developers → Webhooks or through the API. Only https URLs on public hosts are accepted; private and internal addresses are refused at registration. The signing secret (whsec_…) is shown once at creation and can be rotated at any time; after a rotation, verify against both secrets until your deploy has caught up.
Events
Twelve event types, subscribable individually or with *:
scan.completed · scan.failed · tracker.new · issue.opened · issue.resolved · alert.raised · config.published · version.promoted · export.completed · declaration.updated · install.broken · plan.changed
Verifying a delivery
Deliveries follow the Standard Webhooks convention. Three headers arrive with each POST:
webhook-id: <delivery id>
webhook-timestamp: <unix seconds>
webhook-signature: v1,<base64(hmac_sha256(key, id.timestamp.body))>
The HMAC key is the base64 part of your whsec_ secret, decoded to raw bytes. Recompute the signature over id + "." + timestamp + "." + body, compare in constant time, and reject anything older than five minutes:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret, headers, rawBody) {
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const msg = headers['webhook-id'] + '.' + headers['webhook-timestamp'] + '.' + rawBody;
const want = createHmac('sha256', key).update(msg).digest();
const got = Buffer.from(headers['webhook-signature'].split(',')[1], 'base64');
const fresh = Math.abs(Date.now() / 1000 - Number(headers['webhook-timestamp'])) < 300;
return fresh && want.length === got.length && timingSafeEqual(want, got);
}
Retries and redelivery
A failed delivery retries with backoff: 1 minute, then 5 minutes, 30 minutes, 2 hours, 6 hours; after that it is marked dead. Every attempt, response code and next retry is visible in the endpoint's delivery log, and any delivery can be re-sent by hand; a redelivery references the original. Delivery history is kept for 90 days.
Answering
Respond with any 2xx quickly and do the real work asynchronously; anything else counts as a failure and schedules a retry. Deliveries can arrive out of order and, rarely, more than once; use webhook-id for idempotency.