Encrypt Online
Theme

Passwords & Hashing

Raw Request Bodies for Webhook Verification

Learn why webhook verification often fails after a framework parses the body and how to keep the exact bytes required for HMAC checks.

Encrypt Online Editorial Team3 min read
Encrypt Online guide cover on a lilac background with the headline "Webhooks need the raw body". One request endpoint joins a right-angle incoming arrow at the circle edge. The connector never crosses the circle interior; one clean tangent join replaces the overlapping start. The downward arrow uses a lighter 1.75-unit stroke. Direction-path weight for this drawing: 1.75 units.

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.

Accept the raw body, secret, and signature header, then show the reconstructed signing string, expected digest, and supplied signature.

In brief

What it is: 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.

Worth knowing: 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.

HMAC verification must reproduce each provider's exact signed byte sequence. Stripe includes the timestamp, while GitHub signs the raw body.

The three values you must separate clearly

Keep the raw body, the signature header, and the shared secret or signing key separate. Log only the minimum redacted metadata needed for diagnosis, and preserve the raw body bytes exactly for verification. 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.

After separating the values, parse the header, compute the expected signature over the exact bytes, and compare the values with a timing-safe method when 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 verifier reports malformed headers, timestamp or replay-window failures, and signature mismatches. After a mismatch, check the raw body and secret separately.

See it in a small example

Notice: Those two strings can look identical to a human and still differ at the byte level the provider signed.

Shell
# Good shape: use the raw request body bytes as received.
# Bad shape: JSON.parse(body) followed by JSON.stringify(body) before HMAC verification.

What to verify

  • 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.

Common questions

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.

References