← All tools

Why is my Stripe webhook signature failing?

Paste the raw body, the Stripe-Signature header and your signing secret. It tells you which of the four causes you have — not just that it failed.

The exact bytes Stripe sent. If your framework parsed the JSON and you re-serialised it, the signature will not match even when the secret is right.

Computed locally with Web Crypto. Nothing is sent anywhere — check the network tab.

Tells you which cause it is

A wrong secret, a re-serialised body, an event outside the replay window and a secret rotation all produce the same Stripe error. They need different fixes.

Handles secret rotation

While you roll an endpoint secret, Stripe signs each event with both secrets and sends one v1 per secret. Verifiers that keep only the last one reject valid events for the whole overlap.

Your secret never leaves the page

HMAC is computed locally with Web Crypto. A signing secret is a credential, and most online verifiers ask you to post one to their server.

The four reasons verification fails

Stripe returns the same message — "No signatures found matching the expected signature for payload" — for every failure, which is why this takes so long to debug.

The causes are: the signing secret belongs to a different endpoint or environment; the body is not the exact bytes Stripe sent, usually because a framework parsed and re-serialised the JSON; the event is outside the replay tolerance; or the header carries several signatures because a secret rotation is in progress and your code only checked one of them.

Why the raw body matters so much

The signature covers the exact byte sequence Stripe transmitted. Any middleware that parses JSON and hands your handler an object has already destroyed the information needed to verify it — re-serialising produces different whitespace and key order.

This is why frameworks need a raw-body escape hatch on the webhook route specifically. If verification fails on a body you believe is correct, this is the first thing to rule out.

Secret rotation is the one people miss

When you roll an endpoint's signing secret, Stripe keeps both valid for 24 hours and signs each event twice, sending one v1= element per secret. A verifier that parses the header into a map — Object.fromEntries, or any last-wins parse — keeps only one signature and rejects every event signed by the other secret.

It looks like an outage with no code change behind it, and it resolves on its own when the old secret expires, which makes it easy to misdiagnose as a Stripe problem. Collect every v1 and accept if any matches.

Frequently asked questions

What does "No signatures found matching the expected signature" mean?

That Stripe's computed HMAC did not match any signature in the header. It does not say why. The four causes are a wrong secret, a modified request body, an event outside the timestamp tolerance, or a secret rotation where your code only checked one of several signatures.

Why does verification fail when my secret is definitely right?

Almost always because the body is not the raw bytes Stripe sent. If any middleware parsed the JSON before your handler saw it, re-serialising changes whitespace and key order, and the signature no longer matches.

What is the timestamp tolerance?

Stripe's libraries reject events whose timestamp is more than five minutes from now, to prevent replay attacks. A correct signature can still be rejected if the event is old or your server clock has drifted.

Is my signing secret sent anywhere?

No. The HMAC is computed in your browser with Web Crypto. You can confirm it in the network tab — there is no request.

Why is my Stripe webhook signature failing?

Paste the raw body, the Stripe-Signature header and your signing secret. It tells you which of the four causes you have — not just that it failed.