Prompt
How do I debug webhook signature verification failures?
Latest observation
Webhook signature verification failures usually come down to one of a few issues: the raw payload changed, the wrong signing secret/key is used, the timestamp/clock check failed, or the header parsing differs from what the provider expects.
Here’s a practical debugging checklist:
1) Verify you’re using the raw request body
Most signature schemes sign the exact bytes sent by the provider.
Common mistakes:
- Parsing JSON and then re-serializing it
- Reading the body twice
- Middleware that transforms the payload (pretty-printing, newline changes, Unicode normalization)
What to do:
- Capture the raw body before any parsing
- Compare the raw bytes used for verification to what the provider signed
Example symptoms:
- Works in tests, fails in production
- Fails only after adding JSON middleware or request logging
2) Confirm the correct secret/key
Make sure you’re using:
- The webhook’s endpoint signing secret, not the API secret key
- The correct secret for the correct environment:
- test vs production
- staging vs live
- rotating/old secret vs new secret
Also check:
- Extra whitespace/newlines in env vars
- Copy/paste mistakes
- Base64 vs plain text confusion
3) Check the signature header format
Different providers encode signatures differently:
- Single HMAC value
- Multiple signatures in one header
- Timestamp plus signature
- Versioned headers
Common issues:
- Looking at the wrong header name
- Splitting on the wrong delimiter
- Not handling multiple signatures during key rotation
Log the exact incoming signature header value and compare it to the provider docs.
4) Validate timestamp / clock skew
Many webhook systems include a timestamp to prevent replay attacks.
Failures happen when:
- Your server clock is off
- Requests are delayed too long
- Your allowed time window is too strict
Check:
- NTP time sync on the server
- Timestamp tolerance in your verification code
- Whether the provider sends a signed timestamp separately from the body
5) Ensure the same signing algorithm and encoding
Verify you’re using the exact algorithm:
- HMAC-SHA256 vs HMAC-SHA1 vs Ed25519, etc.
- Hex vs Base64 output
- UTF-8 encoding of the body
- Correct concatenation order of signed components
A very common issue is comparing:
- computed hex digest to
- provider’s base64 signature
6) Compare what you compute with what the provider expects
Log the intermediate pieces:
- raw body length
- timestamp value
- signed payload string
- computed digest
- received signature header
Be careful not to log secrets in plaintext.
A good approach is to compare:
- first 20–30 chars of the computed signature
- the exact canonical string being signed
7) Watch for proxy / platform transformations
API gateways, load balancers, serverless platforms, and reverse proxies can alter requests.
Potential issues:
- Body size limits
- Automatic decompression
- Normalization of line endings
- Character encoding conversion
If using serverless/framework middleware, confirm you can access the raw body:
- Express: use raw-body middleware for the webhook route
- Next.js / serverless: disable default body parsing for that endpoint
- AWS API Gateway/Lambda: handle
isBase64Encodedcorrectly
8) Reproduce with a known-good payload
If the provider has a sample payload and signature:
- Use it to verify your implementation locally
- If sample verification fails, your code is wrong
- If sample works but live traffic fails, the issue is likely raw-body handling or environment mismatch
9) Retry with verbose logging in a safe way
Log:
- request ID / event ID
- headers relevant to verification
- body hash, not necessarily the full body
- computed signature and received signature
- verification result
Avoid logging:
- full secrets
- full payloads if they contain sensitive data
10) Check provider-specific quirks
Some providers have special rules, such as:
- signing the timestamp +
.+ body - canonicalizing JSON fields
- using a particular header version
- signing only selected headers plus body
Always double-check the provider’s exact docs.
Quick triage sequence
If you need the fastest path:
- Confirm you have the correct secret
- Ensure you are using the raw body
- Check the header format
- Verify the timestamp/clock
- Compare algorithm and encoding
A simple debug strategy
Compute and log these three things:
rawBodyHashsignedPayloadStringreceivedSignature
Then verify:
- same raw body bytes as provider
- same secret
- same algorithm
- same timestamp window
If you tell me which webhook provider you’re using (Stripe, GitHub, Shopify, Twilio, etc.) and your stack/framework, I can give you provider-specific debugging steps and sample code.