Webhook signature verification (HMAC SHA-256)
Each webhook request includes a cryptographic signature in the X-Signature-SHA256 header.
To verify the webhook:
- Read the raw request body bytes exactly as received (before JSON parsing / normalisation).
- 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).
- Encode the HMAC digest using the same encoding we use (see “Signature format” below).
- Constant-time compare your computed signature with the X-Signature-SHA256 header value.
- 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:
- For HeyLight (Switzerland): [email protected]
- For HeyLight (Italia): [email protected]
