Dev Tools

How to Debug a Failing Webhook HMAC Signature Locally in 2026

Your webhook handler says the signature doesn't match, but you swear the secret is right. Here's how to isolate the problem in under a minute.

📅 Aug 5, 2026·⏱️ 6 min read·✍️ Cikal Studio Labs
🔏

Why webhooks use HMAC signatures

When Stripe, GitHub, or any other service sends your server a webhook, it needs a way to prove the request actually came from them and wasn't forged or tampered with in transit. The standard solution is an HMAC signature: the provider computes HMAC-SHA256(sharedSecret, rawRequestBody) and sends the result in a header (commonly X-Hub-Signature-256: sha256=<hex> for GitHub, or Stripe-Signature: t=...,v1=<hex> for Stripe). Your server recomputes the same HMAC using the shared secret and compares it to the header value. If they match, the payload is authentic and untampered.

The most common reason verification fails

By far the most frequent cause of "my signature doesn't match" bugs is re-serialization. Many web frameworks automatically parse the incoming JSON body into an object before your handler code ever sees it. If you then re-stringify that object to compute the HMAC, you are hashing different bytes than what the provider actually sent — different key ordering, different whitespace, different number formatting can all silently change the byte sequence. HMAC is byte-exact: even a single space difference produces a completely different signature. The fix is almost always to capture and hash the raw, unparsed request body before any JSON parsing happens.

Isolating the bug with a standalone verifier

When you suspect a signature mismatch, the fastest way to isolate whether the bug is in your HMAC logic or somewhere else (secret loading, header parsing, raw-body capture) is to test the HMAC computation in isolation, outside your actual server code. Paste the exact raw payload bytes, your signing secret, and the signature you received into a standalone HMAC-SHA256 calculator. If it matches there but not in your app, the bug is almost certainly in how your app captures the raw body or loads the secret — not in the cryptography itself.

A quick debugging checklist

  1. Confirm you're hashing the exact raw request body — not a re-parsed/re-stringified copy.
  2. Check for trailing whitespace or newline differences between what you're hashing and what the provider actually sent.
  3. Double check you're using the correct secret for the correct mode — test-mode secrets don't verify live-mode payloads and vice versa.
  4. Confirm you're comparing hex-decoded bytes with a constant-time comparison, not a naive string equality that could be case-sensitive when it shouldn't be.
  5. If your provider sends a prefixed header value like sha256=<hex> or t=...,v1=<hex>, make sure you're stripping the prefix before comparing.

Once you've confirmed the raw math is correct with a standalone tool, any remaining mismatch in production almost always traces back to how the raw body is captured in your specific framework — a much narrower, more tractable bug to chase down.

Frequently Asked Questions

Does this tool send my secret key anywhere?

No. The HMAC-SHA256 computation happens entirely in your browser using the native Web Crypto API (crypto.subtle). Your payload and secret are never transmitted anywhere.

Is there a tool that can verify a webhook HMAC signature online?

Yes — Webhook Signature Verifier computes the expected HMAC-SHA256 signature from your payload and secret, then compares it to your received signature, supporting sha256= and v1= prefixed formats. It's a one-time $4.99 purchase — no subscription, no account required.

Which webhook providers does this work with?

Any provider using the standard HMAC-SHA256 pattern, which includes GitHub, Stripe, and most modern webhook systems. It supports raw hex signatures as well as the sha256= (GitHub-style) and v1= (Stripe-style) prefixed header formats.

My signature matches here but still fails in production — what's wrong?

The most common cause is your server re-serializing the JSON body before hashing it, which changes the bytes. Make sure your handler hashes the exact raw, unparsed request body — not a re-stringified copy of the parsed object.

Can I use this for algorithms other than HMAC-SHA256?

This tool is purpose-built for HMAC-SHA256, the most widely used webhook-signing algorithm. It does not support other hash functions or asymmetric signature schemes.