How webhook signing works
Every webhook delivery includes anX-Conduit-Signature header in the format:
v1 is HMAC-SHA256(<unix-timestamp>.<raw-body>, secret), computed over the raw request bytes before any JSON parsing. The signing secret includes the whsec_ prefix; pass it verbatim, never strip it.
A delivery normally carries one v1. While you are rotating the endpoint’s signing secret, it carries two v1 values for a grace period — one signed with your new secret and one with the previous one — so deliveries keep verifying while you roll your secret over.
To verify:
- Parse
tand everyv1from theX-Conduit-Signatureheader. - Re-compute HMAC-SHA256 of
<t>.<raw-request-body>using your endpoint secret (fullwhsec_...string as the key). - Constant-time compare the result against each
v1; accept if it matches any of them. - Reject signatures where
tis older than 300 seconds to protect against replay.
Verify locally
Use either snippet below. Replace the three constants with your actual values.Node.js / Bun
Python 3
crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) and enforce the 300-second replay window.
See also
- Webhooks reference - full signature scheme and event topics
failureMessagesymmetry contract