Signature Verification

Webhook signature verification (HMAC SHA-256)

Each webhook request includes a cryptographic signature in the X-Signature-SHA256 header.

To verify the webhook:

  1. Read the raw request body bytes exactly as received (before JSON parsing / normalisation).
  2. Compute an HMAC using SHA-256 with your shared secret key and the body as the message (NOTE: the keys in the body must be ordered alphabetically to guarantee parity with the server side signature generation).
  3. Encode the HMAC digest using the same encoding we use (see “Signature format” below).
  4. Constant-time compare your computed signature with the X-Signature-SHA256 header value.
  5. If they match, you can trust the webhook is genuine; if not, reject with 401/403.

What you need

Shared secret key: the webhook signing key you obtained from us (treat it like a password).

Header: X-Signature-SHA256 (string).

Raw request body: bytes as received.

Signature format

We sign the webhook body using:

Algorithm: HMAC-SHA256

Message: raw HTTP request body (bytes)

Key: your shared secret

Encoding: hex (lowercase)

hex( HMAC_SHA256(secret, raw_body) )
🚧

Many signature failures come from hashing the parsed JSON rather than the raw body, or from using the wrong

Common pitfalls

  • Do not re-serialise JSON and then sign it — whitespace and key ordering will change the digest.
  • Ensure you’re using the raw body bytes, not req.body / parsed objects.
  • Ensure you’re comparing like-for-like: hex vs base64 and any prefix (we use plain lowercase hex, no prefix).
  • Use a constant-time compare (timingSafeEqual, compare_digest, hash_equals, etc.) to avoid leaking information.

Recommended response behaviour

  • If the signature is missing or invalid: return 401 Unauthorized (or 403) and do not process the event.
  • If valid: process the event and return a 2xx response.

Need help?

For any questions related to the integration, please contact: