This exact error string sends people to Stack Overflow constantly: No signatures found matching the expected signature for payload. It's Stripe's constructEvent telling you the HMAC it computed doesn't match the v1 value in your Stripe-Signature header. Almost always one of four things:
-
Wrong secret. Each endpoint (and each of live/test mode) has its own
whsec_.... Pasting the secret from a different endpoint, or from test mode into a production handler, is the single most common cause. -
Body already parsed as JSON before verification.
express.json()or similar middleware re-serializes the body — different key order or whitespace — before your webhook route sees it. Stripe signs the exact raw bytes it sent, so you need the untouched raw body on that specific route. - A proxy or CDN normalizing the body. Re-encoded gzip or rewritten line endings change the byte length. Compare what your handler receives against the length in Stripe's Dashboard webhook attempt log.
-
Checking
v0instead ofv1. The header carries both; Stripe's own SDKs verifyv1.
Instead of guessing which one applies to you, I built a tool that does the actual HMAC-SHA256 computation in your browser (Web Crypto API, nothing sent to any server) and tells you exactly where the mismatch is:
Paste your raw payload, your Stripe-Signature header, and your signing secret — it recomputes the signature client-side and shows you whether it's a secret mismatch, a missing v1, or something else.
If it points at the fix, that's it, you're done for free. If you want the other 6 checks we run before calling a Stripe integration production-ready (idempotency, Checkout session edge cases, Charges vs PaymentIntents), that's a one-page checklist: 7-point checklist, $2.
Top comments (0)