Verifying Webhook Signatures
A signature proves a delivery really came from the provider and arrived unaltered. Verifying it is non-negotiable.
What you'll learn
- Explain why signatures are essential for webhook security
- Describe how an HMAC signature is produced and checked
- Verify against the raw request body, not a re-serialised copy
- Compare signatures safely and guard against replay
7 min read
Why signatures exist
Your webhook endpoint is a public URL — anyone who discovers it can send it a request that looks just like a real delivery. A signature is what lets you tell a genuine event from a forgery. The provider computes a cryptographic value over the payload using a secret that only the two of you share, and sends that value in a header. You recompute the same value yourself and compare the two.
If they match, you learn two important things at once: the request came from someone holding the shared secret, and the body was not tampered with anywhere in transit. If they do not match, you reject the request outright. Without this check, an attacker who guessed or found your endpoint could feed your system entirely fabricated events, and you would have no way to know.
How HMAC verification works
The most common scheme is an HMAC: a keyed hash computed over the request body. The provider runs HMAC(secret, body) and sends the resulting digest in a header. You repeat the exact same calculation, with the same secret, over the bytes you received, and check that your result equals theirs.
const expected = hmacSha256(secret, rawBody);
const provided = request.headers['x-signature'];
if (!timingSafeEqual(expected, provided)) {
return respond(401);
}Crucially, the secret never travels over the wire — only the signature does. So even an attacker who can observe all of your traffic still cannot forge a valid signature, because they never see the key needed to produce one. That asymmetry is the whole point.
Verify the raw body
Here is a subtle but absolutely critical rule: sign and verify the exact bytes of the request body. If your web framework parses the JSON for you and you then re-serialise that object to verify against, key ordering or whitespace can shift, and the signature you recompute will no longer match the one the provider sent. Capture the raw body before any parsing and verify against that untouched copy.
This is, by a wide margin, the most common reason signature verification fails in practice. Many frameworks need explicit configuration to expose the raw payload alongside the convenient parsed object, and developers miss it. Get this one detail right and the overwhelming majority of signature headaches simply disappear, as also noted in securing webhook endpoints.
Compare safely and stop replays
Always use a constant-time comparison when checking signatures. A naïve string equality can leak, through tiny timing differences, how much of the signature matched before it diverged — and a patient attacker can exploit that signal to reconstruct a valid signature byte by byte. A constant-time comparison removes the timing difference entirely and closes that door.
Signatures often cover a timestamp as well as the body. Reject any delivery whose timestamp is too old to blunt replay attacks, where an attacker captures one valid request and resends it later. Pair this with idempotency for defence in depth. The provider's exact header names, hashing algorithm, and timestamp scheme are all set out in the API documentation.
Key takeaways
- Signatures prove both the origin and the integrity of a delivery
- Recompute the HMAC with the shared secret and compare results
- Always verify against the raw body bytes, not parsed JSON
- Use constant-time comparison and reject stale timestamps
FAQ
Why does my signature check keep failing?
Most often because you verified a re-serialised body. Capture and hash the exact raw bytes you received, before any JSON parsing changes whitespace or key order.
Can I skip verification over HTTPS?
No. HTTPS protects data in transit but does not prove the sender is the provider. Anyone can POST to your public URL, so signature verification is still required.
What should I do if verification fails?
Reject the request with a 401 and do not process the payload. Log the attempt so you can investigate repeated failures, which may indicate misconfiguration or probing.
Ready to build?
Read the API reference, grab the OpenAPI spec, and ship a resilient integration.