Skip to main content

How webhook signing works

Every webhook delivery includes an X-Conduit-Signature header in the format:
Where each 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:
  1. Parse t and every v1 from the X-Conduit-Signature header.
  2. Re-compute HMAC-SHA256 of <t>.<raw-request-body> using your endpoint secret (full whsec_... string as the key).
  3. Constant-time compare the result against each v1; accept if it matches any of them.
  4. Reject signatures where t is older than 300 seconds to protect against replay.
See the webhooks reference for the full signing-and-verification protocol.
Do not paste production endpoint secrets into any hosted page. Use a sandbox endpoint secret or a dummy value when testing verification locally.

Verify locally

Use either snippet below. Replace the three constants with your actual values.
Node.js / Bun
Python 3
Both snippets implement constant-time comparison (crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) and enforce the 300-second replay window.

See also