Every delivery carries three headers:
The algorithm
- Read the raw request body as bytes/text — do not re-serialise
the parsed JSON, or whitespace/key-order differences will break the
signature.
- Reject the request if
now - IC-Timestamp is greater than 300
seconds (5-minute replay window).
- Compute
HMAC_SHA256(secret, "{IC-Timestamp}.{raw body}") as hex.
- Compare it to
IC-Signature with a constant-time comparison.
- Only if it matches, parse the body and dedupe on
IC-Event-Id.
The secret is the whsec_… value shown once when you created the
endpoint.
Node.js example
Verify the signature on the raw body, before parsing. Frameworks
that auto-parse JSON (Express express.json(), Next.js route handlers)
may not expose the raw bytes by default — configure a raw-body reader
for your webhook route.
If verification fails
Return a 4xx and do nothing else. A failed signature means the request
did not come from us (or was tampered with) — never process it. If
legitimate events start failing verification, confirm you’re using the
right endpoint’s secret and signing over the raw body.