Webhooks
Webhooks push events to an endpoint you host, instead of you polling the API. Register an endpoint at
Developer → Webhooks (/settings/webhooks) — provide your HTTPS URL and choose which event types
to receive.
The delivery headers
Each delivery is an HTTP POST with a JSON body and these headers:
| Header | Meaning |
| --- | --- |
| X-Webhook-Signature | The HMAC signature (see below). |
| X-Webhook-Timestamp | The Unix timestamp the signature was computed over. |
| X-Webhook-Event | The event type (the catalog event key). |
| X-Webhook-Id | The delivery id — use it as a dedupe key. |
Verifying the signature
Deliveries are signed Stripe-style: an HMAC-SHA256 over ${timestamp}.${rawBody} keyed by the
endpoint's signing secret. The X-Webhook-Signature header value has the form t=<timestamp>,v1=<sig>.
To verify, recompute the HMAC over the X-Webhook-Timestamp and the exact raw request body you
received, and compare it to the v1= value:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, signatureHeader, timestampHeader, signingSecret) {
const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
const expected = createHmac('sha256', signingSecret)
.update(`${timestampHeader}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}Always verify the signature against the raw, unparsed body before acting on a delivery — and reject
deliveries whose X-Webhook-Timestamp is too old to limit replay. The signing secret is shown on the
Webhooks settings page; treat it like an API-key secret.
Retries and redelivery
A delivery that does not get a 2xx is retried automatically on an exponential backoff. After the
maximum number of attempts, the delivery is marked dead (dead-lettered). You can inspect every
attempt — and manually redeliver any delivery — from the Webhooks page in the app.
Respond 2xx quickly and do your heavy work asynchronously, so a slow handler does not cause a retry.