Skip to content
← cd ../posts
[Developer Tools]2 min read

Webhook Signatures: Debug HMAC Verification Failures

A practical workflow for debugging HMAC webhook signature failures across raw request bodies, headers, timestamps, secrets, encodings, and replay windows.

Sagar Kumar Sethi
HMAC webhook signature debugging workflow with signed payload lanes, secret vault, digest comparison, and verification checks

Webhook signature failures are frustrating because the error usually says one thing: invalid signature. That message hides many possible causes. The secret may be wrong, the body may have been parsed before signing, the timestamp may be outside the allowed window, or the signature may be encoded differently than you expect.

HMAC verification only works when both sides sign the exact same bytes with the exact same secret and compare the exact same digest format.

Start With the Contract

Before changing code, read the webhook provider's signature rules and write down the contract in your own words.

  • Which header contains the signature?
  • Is there a timestamp header?
  • What string or bytes are signed?
  • Which hash algorithm is used?
  • Is the digest hex, Base64, or prefixed?
  • What replay window is allowed?

Most webhook bugs come from one of those details being slightly wrong.

The HMAC Debugging Checklist

1. Use the Raw Request Body

The most common mistake is verifying a parsed and re-serialized body instead of the raw body that arrived over HTTP.

JSON parsing can change whitespace, key order, escaping, and line endings. The object may mean the same thing, but the bytes are different. HMAC does not verify meaning. It verifies bytes.

If your framework parses the body automatically, configure the webhook route to preserve the raw body for signature verification.

2. Confirm the Secret

Secrets are easy to mix up between environments. Check the exact environment:

  • Local
  • Staging
  • Production
  • Test webhook endpoint
  • Live webhook endpoint

Do not log the full secret. Log whether the secret exists, which environment loaded it, and maybe a short fingerprint such as the first few characters of a hash of the secret.

3. Rebuild the Signed Payload Exactly

Many providers sign more than the body. Some include timestamps, version prefixes, delimiters, or header values.

javascript
const signedPayload = `${timestamp}.${rawBody}`

That dot, newline, or prefix matters. If the provider signs timestamp.body and your code signs only body, the digest will never match.

4. Check Digest Encoding

The same HMAC bytes can be represented in multiple formats:

  • Hex
  • Base64
  • Base64 URL-safe
  • Prefixed hex
  • Multiple signatures in one header

Before comparing, normalize the expected and received signatures into the same representation. Do not compare a hex string against a Base64 string and assume the algorithm is wrong.

5. Use Constant-Time Comparison

Once the strings are normalized and lengths are checked, use a constant-time comparison helper. That prevents timing leaks and avoids subtle comparison mistakes.

In Node.js, this usually means comparing buffers with a timing-safe helper after validating equal lengths.

6. Validate the Timestamp Window

Signature verification often includes replay protection. A valid signature may still be rejected if the timestamp is too old or too far in the future.

When debugging locally, check system time, time zones, and clock drift. Log the received timestamp and computed age, not just "timestamp failed."

7. Keep a Known Good Fixture

Create one sanitized fixture with:

  • Raw body
  • Headers
  • Secret fingerprint
  • Expected signed payload description
  • Expected signature

Use it in a unit test. A known good fixture makes future framework upgrades, body parser changes, and dependency changes much safer.

Use an HMAC Tool Carefully

Use the HMAC Generator at /tools/hmac-generator/ when you need to compare digest formats or test a sanitized fixture. Do not paste production secrets or live webhook payloads into tools you do not control.

Webhook signatures are strict by design. When verification fails, debug bytes, secret, payload construction, digest encoding, and timestamp checks in that order.

Related Posts

Useful Tools For This Topic

explore_all →