WebhookToolkit
Stripe · GitHub · Shopify · Slack

Webhook signature validator

Last updated: July 2026

Your provider sends a signature header, your handler says it does not match, and the docs answer with an SDK to install. That does not tell you why. Paste the signature, your secret and the raw body below: we recompute the HMAC in your browser and, when it fails, retry the handful of mutations that usually cause it — a stray space in the secret, a trailing newline, a re-serialised body, a stale timestamp.

In short: a webhook signature fails almost always for one of five reasons — wrong secret (test vs live), a body your framework re-serialised, a timestamp outside the 5-minute replay window, a trailing newline, or a leftover sha256= / v0= prefix. This tool recomputes the HMAC and tells you which one it is.

Paste a failing signature

Everything runs in your browser with the Web Crypto API. Your signing secret is never sent to our servers — you can verify in the Network tab.

The four signature schemes

All four are HMAC, but they differ in what gets hashed and how the result is encoded. Get one detail wrong and every delivery fails.

ProviderWhat gets signedAlgorithm
StripeHMAC of t.body; signature and timestamp share one Stripe-Signature headerSHA-256 hex
GitHubHMAC of the raw body; signature in X-Hub-Signature-256SHA-256 hex
ShopifyHMAC of the raw body; signature in X-Shopify-Hmac-Sha256SHA-256 base64
SlackHMAC of v0:timestamp:body; needs the timestamp header tooSHA-256 hex

Twilio is the odd one out — it hashes a canonical URL plus sorted params, not the body. Use the Twilio signature validator for that.

The five real causes

Ranked by how often they show up in support threads.

#CauseSymptomFix
1Wrong secretEvery delivery fails, even a fresh oneTest vs live mismatch — providers issue a different signing secret per mode and per endpoint.
2Re-serialised bodyFails after your framework parsed the JSONSign the raw bytes, not JSON.stringify(parsed): spacing and key order change the hash.
3Stale timestampPasses in tests, fails on replayed eventsStripe and Slack reject anything older than 5 minutes. Do not replay a captured event hours later.
4Trailing newlineOff-by-one body, signature never matchesA proxy or editor appended a newline. This tool flags that exact case.
5Prefix compared literallysha256= or v0= included in the comparisonCompare only the hex/base64 part, not the scheme prefix.

The raw-body trap

The number-one cause: you verify against the parsed-then-re-serialised body instead of the exact bytes. JSON.stringify(JSON.parse(body)) changes spacing and key order, so the HMAC no longer matches. Read the raw body before anything parses it.

Express
// Express: keep the RAW bytes, do NOT let JSON re-serialise them
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const raw = req.body;                 // Buffer, exactly as received
  verify(raw, req.headers['stripe-signature'], secret);
});
Flask
# Flask: request.get_data() returns the raw body; request.json does NOT
raw = request.get_data()               # bytes, untouched
verify(raw, request.headers['X-Hub-Signature-256'], secret)
Next.js App Router
// Next.js App Router: read the text before parsing
export async function POST(req) {
  const raw = await req.text();         // sign THIS, not JSON.stringify(await req.json())
  verify(raw, req.headers.get('stripe-signature'), secret);
}

Rule of thumb: capture the signature and the raw bytes at the edge, verify, then parse.

Timestamps and replays

Stripe and Slack fold a timestamp into the signed string and reject anything older than five minutes — that is replay protection, not a bug. GitHub and Shopify do not, so a valid GitHub signature stays valid forever. If the tool says valid but expired, your secret and body are fine; you are just replaying an old event. Regenerate a fresh delivery instead of re-sending a captured one.

Once the signature checks out

Need to send a correctly-signed test event to your own endpoint? The webhook signer builds one for all six providers. To capture and inspect real deliveries, point them at a Relay tunnel.

Open the signerTwilio signature validatorRelay

Frequently asked questions

Do you store my signing secret?

No. The HMAC is computed in your browser with Web Crypto; the secret and body never reach our servers. Open the Network tab and you will see no request goes out when you click Check.

Which providers are supported?

Stripe, GitHub, Shopify and Slack — the four HMAC-over-body schemes. Twilio signs a canonical URL instead, so it has its own tool: the Twilio signature validator.

It passes here but my server still rejects it. Why?

Almost always the body. Your framework parsed the JSON and you re-serialised it before verifying, which changes spacing and key order. Sign the raw bytes — see the Express, Flask and Next snippets above.

What does 'valid but expired' mean?

The HMAC is correct, but Stripe and Slack also refuse signatures older than five minutes to block replays. If you captured an event and send it again later, verification fails on the timestamp even though the secret is right.

Webhook signature validator: Stripe, GitHub, Shopify, Slack · Webhook Toolkit