Why Webhook Signature Verification Fails
Debug webhook signature failures by preserving the raw body, checking timestamp headers, payload canonicalization, secret selection and encoding boundaries.
Quick Answer
Webhook signature verification usually fails because the verifier signs different bytes from the provider. Preserve the raw request body, use the exact timestamp and signature header format, choose the correct endpoint secret and avoid parsing or pretty-printing JSON before verification.
Example Scenario
A webhook endpoint receives events, but every event fails signature verification. The JSON body looks correct when logged, the secret was copied from the provider dashboard, and retrying the event does not help. The mismatch is often caused by body parsing, altered whitespace, wrong secret, wrong timestamp string or comparing the wrong digest format.
Step-by-Step Explanation
- Capture the raw body bytes before any JSON parser changes them.
- Read the provider documentation for the exact signed string format.
- Verify that the endpoint secret matches the environment and webhook endpoint.
- Check timestamp tolerance and clock drift.
- Compare hex, Base64 and prefixed digest formats carefully.
- Use a constant-time comparison after computing the expected signature.
The Raw Body Is the Evidence
Many webhook systems sign the exact request body. If your framework parses JSON and then serializes it again, the bytes can change even when the visible data looks identical. Whitespace, key order, trailing newlines and Unicode normalization may all affect the signature.
The most important implementation detail is where verification happens. It should happen before middleware consumes or transforms the body. If the framework gives you only a parsed object, you may need a raw-body option for that route.
Logging the parsed object is not enough proof. It shows what your application understood, not what the provider signed.
Providers Sign Different String Formats
Some providers sign only the raw body. Others sign a timestamp, a dot, and the raw body. Some include version prefixes in the signature header. Others send multiple signatures for rotation. A verifier copied from another integration can be almost correct and still fail every request.
Read the exact format and reproduce it literally. If the docs say timestamp + "." + body, the separator matters. If the header says v1=, remove or include that prefix exactly as the algorithm expects.
When debugging, print safe metadata: timestamp string, body byte length, algorithm name and digest format. Avoid printing full secrets or full payment payloads.
Secret Selection Is Easy to Get Wrong
Webhook dashboards often have separate secrets for development, staging, production, test mode, live mode and individual endpoints. A secret from one endpoint may not verify events from another endpoint even inside the same account.
If verification fails after a deployment, confirm which endpoint URL the provider is calling and which secret is loaded in that environment. Environment variables with trailing whitespace can also produce a correct-looking but wrong secret.
During rotation, providers may send signatures that match either the old or new secret. Your verifier should follow the provider guidance rather than deleting the old secret too early.
Timestamp Checks Can Fail Independently
Many webhook signatures include a timestamp to reduce replay risk. The cryptographic digest may be correct while the timestamp check fails because the server clock is wrong or the event was replayed outside the allowed tolerance.
Separate digest mismatch from timestamp rejection in logs. Both should fail verification, but the operational fixes are different. Digest mismatch points to body, secret or algorithm. Timestamp rejection points to clock sync, retry delay or replay handling.
Keep the tolerance reasonable. A huge tolerance weakens replay protection; a tiny tolerance can reject legitimate delayed retries.
Encoding and Comparison Details Matter
Computed HMAC bytes can be represented as hex, Base64 or Base64URL. The provider header may include prefixes, comma-separated values or multiple versions. Comparing the wrong representation gives a mismatch even when the underlying bytes are correct.
After computing the expected signature, compare it with a constant-time comparison where available. This is not the first debugging issue most teams hit, but it is the right habit for authentication-related checks.
Do not trim signature headers casually. Remove documented prefixes and separators, but preserve the value format the provider expects.
Framework Middleware Can Consume the Body First
Webhook failures often appear after a framework upgrade or hosting migration because the request body path changes. A route that previously received raw bytes may now receive a parsed object, a decoded string or a stream that has already been read by logging middleware.
Check the order of middleware. Body parsers, request loggers, compression handlers and security scanners can all touch the body before the webhook verifier runs. If the verifier receives a transformed body, the signature comparison is no longer checking what the provider signed.
A good endpoint isolates webhook verification from ordinary JSON API handling. It can still parse JSON after verification, but the raw-body capture should be the first step that depends on the incoming request body.
What to Check Next
Once verification succeeds, parse the JSON and validate event type, idempotency key and account context. Signature verification proves the event came from the provider, not that your application should process it twice or accept every event type.
Use Text Diff Checker for safe redacted comparisons of raw-body metadata and Hash Generator only for local digest experiments. Production webhook secrets should stay out of shared tools and tickets.
Add one test vector from the provider docs or a captured safe sample. A test vector protects the raw-body path when middleware or hosting configuration changes later.
Also test the negative path. A changed byte, old timestamp, wrong secret and malformed signature header should all fail for clear reasons. Those failures give you confidence that the verifier is not accidentally accepting events because an error path was too permissive.
Keep the webhook route small. The more application logic that happens before verification, the more places there are for body transformation, logging side effects or accidental parsing to change the evidence you need.
For operations, count verification failures by provider, endpoint and reason. A sudden increase in timestamp failures suggests clock or retry behavior. A sudden increase in digest mismatches suggests body handling, secret rotation or provider configuration. Separating those counters prevents every webhook incident from looking identical.
Code Examples
const rawBody = await request.text();
const signature = request.headers.get('x-provider-signature');
verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET);
const event = JSON.parse(rawBody); const timestamp = request.headers.get('x-provider-timestamp');
const signedPayload = timestamp + '.' + rawBody;
const expected = hmacSha256Hex(secret, signedPayload); console.log({
bodyBytes: new TextEncoder().encode(rawBody).length,
hasSignature: Boolean(signature),
timestamp,
secretConfigured: Boolean(process.env.WEBHOOK_SECRET)
}); Common Mistakes
- Verifying a parsed and reserialized JSON object.
- Using a staging webhook secret for production events.
- Dropping the timestamp or separator from the signed string.
- Comparing hex output with a Base64 signature header.
- Logging full webhook secrets while debugging.
FAQ
Why does parsed JSON break webhook verification?
Parsing and reserializing can change bytes. Many providers sign the exact raw request body.
Can the secret be correct but verification still fail?
Yes. Wrong signed-string format, timestamp handling or digest encoding can still cause a mismatch.
Should I verify before or after parsing JSON?
Verify before parsing when the provider signs the raw body.
What should I log during debugging?
Log safe metadata such as byte length, timestamp presence and algorithm name, not full secrets or sensitive payloads.