Skip to content
cascadehook.

ToolsFree tool

Stripe webhook signature verifier

Check a Stripe-Signature header against your endpoint's signing secret and see exactly why verification fails. Nothing you paste leaves your browser.

Paste request headers

Paste the headers from a log, a request inspector, or curl -v, as Name: value lines or JSON. The matching fields below are filled in.

Signature your code computed (optional)

If your code rejects a request that verifies here, print the signature it computes for this request and paste it. The tool finds which mistake produces that value.

Runs entirely in your browser with Web Crypto. Nothing you paste is sent anywhere or saved.

The result shows up here

Paste the endpoint's signing secret, the Stripe-Signature header, and the raw body from a request, then verify. To see how it works first, use Try an example.

What gets signed

HMAC-SHA256(secret, t + "." + body)

“No signatures found matching the expected signature for payload”

Stripe's libraries raise this error for every mismatch, so on its own it doesn't say what's wrong. One of three inputs is: the raw body, the Stripe-Signature header, or the endpoint secret. Paste all three above to find out which.

The body was parsed before it was verified

The most common cause. If express.json() runs before your webhook route, the body is already an object, and serializing it again changes its bytes. Register the webhook route with express.raw({ type: "application/json" }) before app.use(express.json()). In a Next.js route handler, read await request.text(); in the Pages Router, turn off bodyParser and read the raw buffer. Behind API Gateway, pass the raw body through to your Lambda function.

The secret is from a different endpoint

Every endpoint has its own whsec_ secret, and test-mode and live-mode endpoints never share one. stripe listen prints a secret of its own, separate from any Dashboard endpoint, so a secret that works locally fails in production, and the other way around.

The secret was changed on its way to the HMAC

Stripe uses the whole whsec_… string as text for the key. Stripping the prefix, Base64-decoding the rest (which is how Standard Webhooks secrets work), or loading it from an .env file with a trailing newline or quotes all change the key.

Only part of the header is checked

The header holds a timestamp and one or more signatures: t=…,v1=…,v1=…. Split it on commas, sign with the t value, and accept the request if any v1 entry matches. Ignore v0; it's a placeholder on test events.

The timestamp is outside the tolerance

Stripe's libraries reject requests signed more than five minutes ago. Every retry is signed again with a fresh timestamp, so this usually means a request was replayed by hand or the server's clock has drifted. Keep it synced with NTP.

Sending webhooks from your own product? Cascadehook signs every delivery with Standard Webhooks, retries failures for up to 72 hours, and keeps every attempt for replay. Create a project or try the Standard Webhooks verifier.

Stripe is a trademark of Stripe, Inc.. Cascadehook isn't affiliated with or endorsed by Stripe.