Webhook signature verification: why the raw body matters more than your parsed JSON
Learn why webhook verification often fails after a framework parses the body and how to keep the exact bytes required for HMAC checks.

Tip
Keep the exact input bytes stable while you test. One changed newline, encoding step, or parser pass can change a hash or signature.
A webhook verifier usually signs the exact bytes that arrived over HTTP, not your framework’s reconstructed JSON object. That one detail explains a huge number of “signature mismatch” support threads.
This problem works best with a small UI: paste the raw body, paste the secret, inspect the header, and compare what the provider expected with what your server actually used.
Summary
Definition: Webhook signature verification compares a provider-supplied signature with one computed from the exact request body and shared secret or key material.
Why it matters: Verification only works if you preserve the canonical bytes the provider signed. Seemingly harmless JSON parsing can change those bytes.
Pitfall: Whitespace changes, reordering, reserialization, or character-encoding shifts can break verification even when the JSON data is semantically identical.
Why parsed JSON is the wrong input
When a provider says “verify the payload,” it usually means “verify the raw body bytes we sent.” A JSON parser discards that original representation. It may remove insignificant whitespace, reorder object keys during reserialization, or normalize text in ways that look harmless and still change the signed content.
Stripe and GitHub both document this point because it is such a common failure mode. The HMAC is not computed over a data structure. It is computed over bytes.
The three values you must separate clearly
Keep the raw body, the signature header, and the shared secret or signing key separate. Do not log them into one pile and do not normalize them “for convenience.” The raw body is the message. The header tells you how the provider encoded or wrapped the signature. The secret is the verifier input you already control.
Once those values are separated, the problem becomes mechanical: parse the header format, compute the expected signature over the exact bytes, and compare the two values using a timing-safe method where appropriate.
- Raw body: exact bytes as received.
- Signature header: provider-specific wrapper around the expected signature value.
- Secret or key: local verification input.
What a dedicated verifier should teach
A good verifier should not just return “mismatch.” It should tell the user whether the header was malformed, whether the raw body changed, whether the wrong secret was used, and whether a timestamp or replay window is part of the signature contract. This is where a small purpose-built tool can feel authoritative instead of generic.
Quick example
Use this when you want to explain why “I parsed the JSON and then re-stringified it” is not a safe verification path.
What to notice: Those two strings can look identical to a human and still differ at the byte level the provider signed.
# Good shape: use the raw request body bytes as received.
# Bad shape: JSON.parse(body) followed by JSON.stringify(body) before HMAC verification.
Practical check
- Capture the raw body before any JSON parser touches it.
- Parse the signature header exactly as documented by the provider.
- Compare computed and received signatures using a timing-safe method where the platform provides one.
FAQ
If the parsed JSON is the same data, why does verification fail?
Because the provider signs bytes, not abstract JSON meaning.
Can I log the raw body and secret for debugging?
Be careful. Logging those values can create a new secret-handling problem.
Developer workflow
Use this guide as an implementation check before you depend on a digest, password hash, or signature in production logic.
- Freeze the exact input bytes, including encoding and newline handling.
- Generate or verify the digest with a small known sample.
- Record the algorithm, comparison rule, and storage format where future maintainers can find it.
1. exact input bytes
2. hash or HMAC operation
3. constant-format comparison
4. document algorithm and encoding