Webhooks
Standard Webhooks v1, signed and replay-safe.
Outbound deliveries follow the Standard Webhooks v1 specification. Every event is signed with HMAC-SHA256, carries a webhook-id and a webhook-timestamp, and is delivered with up to 8 retries on a fixed backoff. Subscriptions are commands, secrets rotate with overlap, dead-lettered deliveries replay through a typed command.
What it does
Three things this surface gives you.
- Subscriptions are commands (`UpsertWebhookSubscription`). Secrets are returned once on creation and rotate with a 24-hour overlap so you can roll without coordinating a cutover.
- Retries follow a fixed backoff (1s → 5s → 30s → 5m → 30m → 2h → 12h → 24h, max 8 attempts). 5xx and 408/429 retry; other 4xx mark FAILED on the first attempt.
- Dead-lettered deliveries are queryable + replayable via `ReplayWebhookDelivery`; redrive resets the attempt counter without re-running the originating command.
Verify a delivery in TypeScript
Worked example.
// Standard Webhooks v1 verification. Header values:
// webhook-id: evt_01HXR3...
// webhook-timestamp: 1714867200 (epoch seconds)
// webhook-signature: v1,<base64 hmac-sha256>
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWebhook(req: {
headers: Record<string, string>;
rawBody: string;
}, secret: string): boolean {
const id = req.headers["webhook-id"];
const ts = req.headers["webhook-timestamp"];
const sig = req.headers["webhook-signature"];
if (!id || !ts || !sig) return false;
// Reject deliveries older than 5 minutes.
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
// Signed string: `${webhook-id}.${webhook-timestamp}.${rawBody}`
const signed = `${id}.${ts}.${req.rawBody}`;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expect = createHmac("sha256", key).update(signed).digest("base64");
// The header may carry multiple v1, segments during a rotation.
return sig.split(" ")
.filter((s) => s.startsWith("v1,"))
.some((s) => timingSafeEqual(
Buffer.from(s.slice(3)),
Buffer.from(expect),
));
}Preview
Run a working integration path.
Request a guided preview workspace, or bring a sample integration shape to a working session and we will walk through it on a call.