Cosmovex Tools

Stripe webhook signature failing? Inspect the event and verify it in your browser

Stripe with Hookwatch Webhook Tester

This inbox is already set up for Stripe: paste its URL into a Stripe webhook endpoint, trigger a test event, and the request appears here with its raw body and Stripe-Signature header. The Signature tab recomputes the HMAC with your whsec_ secret, so you see whether the signature, the secret or the timestamp is the problem.

  1. Copy the inbox URL at the top of the tool. It already ends in /stripe so Stripe deliveries are easy to spot in the list.
  2. In the Stripe Dashboard go to Developers → Webhooks → Add endpoint, paste the URL and pick the events your handler listens for.
  3. Trigger an event: use Send test webhook on the endpoint, complete a test-mode checkout, or press Send a signed test Stripe event here to see a known-good request first.
  4. Select the request. The Signature tab opens with Stripe selected and reads the t= timestamp and v1= digest from the Stripe-Signature header.
  5. Paste the endpoint's whsec_ signing secret. Valid means the body and secret match; Invalid shows the digest Stripe sent next to the one computed from the raw body.
  6. If it is valid here but your server rejects it, your framework is changing the body before verification: verify against the raw bytes, then parse the JSON.
stripe webhook tester verify stripe webhook signature onlinestripe-signature headerstripe webhook no signatures found matchingtest stripe webhooks without stripe clistripe webhook endpoint url for testing
Open Hookwatch Webhook Tester Free · Pro $5/mo · no account

What to know

Stripe signs every delivery by computing an HMAC-SHA256 over the string made of the timestamp, a dot and the raw request body, using the endpoint's signing secret. The result goes into the Stripe-Signature header as t=<unix time>,v1=<hex digest>. During secret rotation the header can carry more than one v1 value, and any one of them matching is enough. Your server has to rebuild exactly the same string, which is why the raw body matters: a single re-serialised space or reordered key produces a different digest.

The most common cause of 'No signatures found matching the expected signature for payload' is body parsing that runs before verification. In Express, app.use(express.json()) consumes the stream and stripe.webhooks.constructEvent then receives a re-stringified object instead of the original bytes. Mount express.raw({ type: 'application/json' }) on the webhook route only. In Next.js route handlers read await req.text(); in other frameworks look for a raw-body or rawBody option. Middleware that trims, decompresses or re-encodes the body causes the same failure.

The second common cause is the wrong secret. Each endpoint has its own whsec_ value, test mode and live mode use different ones, and stripe listen in the Stripe CLI prints yet another secret that only signs the events it forwards. If the digest here is Invalid with the secret you configured, copy the secret again from the exact endpoint that sent the request. The tool also checks the timestamp against a five-minute window, the same default tolerance the official libraries apply, so a replayed or delayed delivery shows up as a timestamp failure rather than a signature failure.

Stripe retries failed deliveries with exponential backoff for up to three days in live mode, and events can arrive out of order or more than once. Store the event id (evt_…) and skip ones you have already processed, and read the object's current state from the API when ordering matters. The request list here helps: search for an evt_ id or filter with a JSONPath such as $.type == 'invoice.paid' to see every delivery of one event type side by side.

Updated · Cosmovex

Questions

Where do I find the Stripe webhook signing secret?

In the Stripe Dashboard open Developers → Webhooks, select the endpoint and reveal Signing secret. It starts with whsec_. Every endpoint has its own secret, and test mode and live mode endpoints are separate, so copy it from the endpoint that actually sent the request.

Why does Stripe say no signatures found matching the expected signature?

Your code is hashing something other than the raw body Stripe sent, or using a different secret. Body-parsing middleware such as express.json() is the usual cause: verify with express.raw({ type: 'application/json' }) on the webhook route. If the request shows Valid here with your secret, the secret is right and the body handling in your server is the problem.

Can I use this instead of the Stripe CLI?

For inspecting and verifying deliveries, yes: this URL is public, so Stripe can reach it without a tunnel, and requests are kept so you can come back to them (the newest 20 for 24 hours on the free plan, 1,000 for 30 days on Pro). To forward events to code running on your laptop, use Copy as curl (free) and run it in a terminal, or with Pro use Replay to send the stored request to your local server from the browser.

Is my whsec_ secret sent to your server?

No. The Signature tab computes the HMAC with WebCrypto inside your browser. The request itself is stored on our server so it can appear in your inbox, but the secret you paste is only kept in this browser's storage.

What does the timestamp check mean?

Stripe includes the signing time as t= in the header so a captured request cannot be replayed later. The official libraries reject events whose timestamp is more than five minutes from the current time, and the tool applies the same window against the time the request reached the inbox.

The free plan covers everything on this page. Hookwatch Webhook Tester Pro ($5/mo, billed monthly) is described on the Hookwatch Webhook Tester page.