Why raw body mutation breaks webhook verification
Webhook providers sign raw bytes, not your parsed JavaScript object.
That distinction is the source of many "signature verification failed" bugs. The request reaches your app, JSON parsing succeeds, the payload looks correct in logs, and verification still fails because the exact bytes changed before the signature check ran.
What changed
These seemingly harmless transformations can change the signed payload:
- parsing JSON and re-serializing it
- changing whitespace or line endings
- changing key order during serialization
- reading the body stream too early and reconstructing it later
If the provider signed one byte sequence and your verifier checks another, the signature no longer matches.
Why HookLens helps
HookLens captures the request before framework parsing and verifies against the raw body it received. That means it can distinguish between:
- a bad secret
- a missing or malformed signature header
- a genuine signature mismatch
- a likely
body_mutatedcase
That last case is the one most local logs fail to make obvious.
Typical symptoms
You are probably dealing with raw body mutation if:
- the secret is correct
- the header looks valid
- the request payload looks fine in logs
- verification still fails after parsing middleware ran
How to use HookLens to confirm it
Start HookLens with verification enabled for the provider you are testing:
hooklens listen --port 4400 --verify stripe --secret whsec_xxxor:
hooklens listen --port 4400 --verify github --secret ghsecret_xxxIf HookLens reports body_mutated, the likely problem is not the provider's signature. It is your request handling path.
What to fix in your app
The exact fix depends on the framework, but the principle is always the same:
- read the raw request body first
- verify the signature against those raw bytes
- only then parse JSON or transform the payload
If you parse first and verify second, you are already too late.