WEBHOOK SIGNATURE DEBUGGER · RUNS IN YOUR BROWSER

Why is my webhook signature failing?

Paste the request headers, the raw body and your signing secret. The page computes the HMAC the way Stripe, GitHub, Shopify, Slack and Standard Webhooks (Svix, Clerk, Resend) define it, tells you whether it verifies, and, when it does not, tries the usual culprits: re-serialised JSON, a trailing newline, CRLF line endings, whitespace in the secret, a whsec_ secret used in the wrong form.

Nothing is uploaded. Your secret and payload are used in this tab by the browser's Web Crypto API and never leave your device; the page makes no network calls of its own. Prefer a test-mode or rotated secret when you debug.

The request

Fill in the three boxes and click Verify signature.

Replay it locally from the command line

Same engine, zero dependencies, nothing leaves your machine. It can also sign a payload so you can test your own endpoint:

node cli.mjs --body body.json --headers headers.txt --secret whsec_...   # exit 0 if it verifies
node cli.mjs --sign stripe --body body.json --secret whsec_...         # print headers for curl -H
node cli.mjs --provider github --snippet                                # reference verifier

Get cli.mjs and webhook.mjs from the repo. Node 18+. The secret can also come from WEBHOOK_SECRET.

Common questions

Why does my Stripe webhook signature verification fail?

The usual cause is that the body being hashed is not the exact bytes Stripe sent. Frameworks that parse JSON first and then re-serialise it change whitespace and key order, which changes the HMAC. Verify against the raw request body. The second most common cause is the wrong whsec_ secret: each endpoint has its own, and stripe listen prints a different one than the dashboard.

What exactly is signed for each provider?

Stripe signs the timestamp, a dot, then the raw body, using HMAC-SHA256 with the secret and hex output. GitHub signs the raw body (sha256= prefix, hex). Shopify signs the raw body and base64-encodes the result. Slack signs v0:timestamp:body (hex, v0= prefix). Standard Webhooks (Svix, Clerk, Resend and others) sign id.timestamp.body, base64-encode the result, and use the base64-decoded part of the whsec_ secret as the key.

Does this tool upload my webhook secret or payload?

No. Everything is computed in this page with the browser's Web Crypto API. Nothing is sent anywhere and the page makes no network calls. Even so, prefer a test-mode or already-rotated secret when debugging, and rotate any live secret you paste into a shared screen or ticket.

Why does a valid signature still get rejected by my server?

Stripe, Slack and Standard Webhooks libraries also reject requests whose timestamp is more than about five minutes from your server clock. A correct signature on an old request fails that check, which is the replay protection working. Check server time (NTP) if live requests fail this way.

Is this a security audit?

No. It checks one request against one secret and suggests likely causes. It does not review your endpoint's other defences and is not security advice. No warranty.

More: how to verify webhook signatures correctly (and why raw body matters)