πŸ” Webhook Signature Verifier

Paste a payload, enter the shared secret, and check the HMAC-SHA256 signature. Runs 100% in your browser.

Hex accepted with or without a sha256= or 0x prefix. Your payload and secret never leave this page.

-verdict

Signature Check

How to Use This Verifier

Paste the exact raw request body, enter the webhook signing secret, and paste the signature from the request header. The tool computes HMAC-SHA256 locally and tells you whether it matches.

1

Paste the raw request body into the payload box. It must be byte-for-byte what was signed: pretty-printing JSON or trimming whitespace changes the hash.

2

Enter the signing secret exactly as configured with the sender (the Stripe dashboard shows it as whsec_..., GitHub calls it the webhook secret).

3

Paste the signature from the request header. Prefixes like sha256= or 0x are stripped automatically, and comparison is case-insensitive.

4

Read the verdict. The computed HMAC is shown in hex, and the walkthrough lists each of the four verification steps with your actual values.

The Four Verification Steps

1. Take the raw payload bytes (exact request body, UTF-8) 2. Compute HMAC-SHA256(payload, secret) 3. Normalize the received signature (strip sha256= / 0x, lowercase) 4. Compare computed vs received, byte by byte

HMAC-SHA256 mixes the secret with the payload through the SHA-256 hash function twice, so an attacker who sees the payload and signature still cannot forge a signature for a different payload without the secret. That is the whole security of webhooks: the signature is only reproducible by someone holding the secret. Step 3 exists because providers disagree on formatting. GitHub sends sha256=<hex>, others send bare hex or a 0x prefix, so normalization matters. Step 4 should be constant-time in server code so timing cannot leak how many leading bytes were correct.

Worked Example

The standard HMAC-SHA256 test vector uses the message "The quick brown fox jumps over the lazy dog" with the key "key". The correct signature is f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8. Paste that message as the payload, "key" as the secret, and the signature above into the verifier: you will get a match, which proves the tool computes HMAC exactly like Python's hmac, Node's crypto, and PHP's hash_hmac. Change one character of the message and the verdict flips to mismatch, showing how fragile and tamper-evident the scheme is by design.

Common Pitfalls

Most mismatches are formatting bugs, not crypto bugs. Signing the parsed-and-reserialized JSON instead of the raw body is the number one cause: JSON key order and spacing change the bytes. The number two cause is secret confusion, such as using the publishable key instead of the webhook secret, or an old secret after rotation. Number three is timestamp drift for providers like Stripe that include a timestamp in the signed content and reject old ones. When debugging, always log the exact bytes you signed on both sides. This tool signs the payload with UTF-8 encoding, which matches what every mainstream framework does with a text body.

Frequently Asked Questions

How does webhook signature verification work?

The sender computes HMAC-SHA256 over the raw request body using a shared secret and puts the result in a header (Stripe uses Stripe-Signature, GitHub uses X-Hub-Signature-256). Your server repeats the same computation with the same secret and compares the two values. A match proves the payload came from the sender and was not tampered with in transit.

Why does Stripe not send the raw HMAC in its signature header?

Stripe signs a string made of the timestamp, a dot, and the payload (t=...,v1=...), and it includes multiple v1 signatures for key rotation. GitHub sends sha256=<hex>. The HMAC algorithm is the same in all cases; only the signed content and header format differ. This tool verifies the plain HMAC-SHA256 hex form used by GitHub and most custom webhooks.

Do I need to worry about timing attacks when comparing signatures?

For webhook verification, use a constant-time comparison so an attacker cannot learn the correct signature byte by byte from response timing. This tool compares every byte before reporting a verdict. In server code, use hmac.compare_digest in Python, crypto.timingSafeEqual in Node, or hash_equals in PHP.

Why does my signature not match?

The usual causes: extra or missing whitespace (the signature is computed over the exact raw body bytes, so pretty-printing JSON breaks it), the wrong secret or the wrong key version, signing the parsed JSON instead of the raw string, or a prefix like sha256= being included in the comparison. This tool strips sha256= and 0x prefixes automatically, so paste the header value as-is.

Is my secret safe in this tool?

Yes. The HMAC is computed with the Web Crypto API inside your own browser tab. Nothing is uploaded, logged, or sent to any server. You can verify by opening your browser dev tools and watching the network tab: zero requests are made while you verify.