Signature verification
What’s planned
Section titled “What’s planned”When signing ships, every outbound webhook will include two headers:
X-Webhook-Signature: t=1746440100,v1=9f8e7d6c5b4a3928176054321aabbccdd11ee2f3a4c5b6d7e8f9a0b1c2d3e4f5aX-Webhook-Timestamp: 1746440100The signature is base64- or hex-encoded HMAC-SHA256 of:
HMAC-SHA256(webhook_secret, f"{timestamp}.{raw_body_bytes}")The webhook_secret is shown exactly once when generated or
rotated. Argon2id and Fernet ensure the database-stored copy is
not reversible. To rotate, generate a new one, deploy it to your
verifier, and revoke the old one — the dashboard supports
overlapping active secrets.
Replay window
Section titled “Replay window”Reject webhooks whose X-Webhook-Timestamp differs from server
time by more than 5 minutes. This bounds the window in which a
replayed payload can be accepted.
const ageSeconds = Math.abs(Date.now() / 1000 - parseInt(timestamp));if (ageSeconds > 300) throw new Error("webhook too old");Failure modes
Section titled “Failure modes”When signing ships, expect to deal with:
- Missing headers. Reject — the gateway will always set them once enabled.
- Bad signature. Reject — possibly forged, possibly a
webhook_secretrotation that didn’t propagate. - Stale timestamp. Reject — replay attempt or clock skew. Log for investigation; don’t silently drop.
- Body re-parsing. The signature is computed against the raw
body bytes before any framework parses JSON. Use middleware
that exposes the raw body (
express.raw({type: 'application/json'}), FastAPI’sawait request.body()).
See also
Section titled “See also”- Webhooks overview — current recommendations
- Security — the
webhook_secretin the broader key model - Changelog — watch for the signing release