The signature
Kit sends anX-Kit-Signature header with a timestamp and one or more signatures, comma-separated:
t— Unix timestamp (seconds) of when this POST was signed.v1— HMAC-SHA256 of the string"{t}.{raw_body}"(the timestamp, a literal., then the raw request body), keyed with your endpoint’s secret and hex-encoded. During secret rotation the header carries onev1entry per valid secret.
Verifying
Parse the header, rebuild the signed string fromt and the exact bytes you received, compute the HMAC, and compare it against every v1 entry using a constant-time comparison. Reject stale timestamps to guard against replays — 5 minutes is a sensible tolerance (retries are re-signed at send time, so a legitimate late retry still carries a fresh t).
Secret rotation
Rotate an endpoint’s secret at any time withPOST /v4/webhook_endpoints/{id}/rotate_secret — the response is the only place the new secret appears in plaintext. To avoid dropping deliveries while you roll it out, the old secret keeps verifying until the overlap window closes (the endpoint’s previous_secret_expires_at). During the window Kit signs with both secrets, so the header carries two v1 entries:
v1 value matches — do that and your verification keeps working across a rotation with no code change. Once you’ve switched to the new secret you can close the window early with POST /v4/webhook_endpoints/{id}/revoke_previous_secret.
Rotating again while a previous rotation’s window is still open returns
409 — pass force: true to rotate anyway, immediately expiring the older secret. A retried delivery is signed with whichever secrets are valid at send time, so a late retry that lands after the window closes carries a single v1.Idempotency
The same event can arrive more than once — a retry re-POSTs the whole delivery after your server was briefly unreachable, or an upstream job retry re-emits the event in a fresh delivery. Every event carries a UUIDid that stays the same across re-sends: record it and skip any event you’ve already processed. Design your handler to be safe to run twice regardless.
Deduplicate on the event
id, not on delivery_id — a re-emitted event can arrive under a different delivery.