What it means
Stripe signs every webhook so you know it's genuine. Your check failed, so your code rejected the event. Result: people pay, but your app never hears about it ("charged but no access").
Why AI tools cause it
Two causes cover almost every case:
- The body was changed before checking. Signature checks need the exact raw request body. Frameworks often parse it into JSON first, which breaks the check.
- Wrong signing secret. The
whsec_...secret from the Stripe CLI (local testing) is different from the one for your live webhook endpoint in the Stripe dashboard.
The fix
- Pass the raw body to
stripe.webhooks.constructEvent. In Next.js App Router, useawait req.text(), notreq.json(). In Express, useexpress.raw({ type: 'application/json' })on the webhook route. - Copy the signing secret from the specific endpoint in Stripe → Developers → Webhooks and set it as an environment variable on your host. Redeploy.
- Test and live mode each have their own secret. Use the one that matches.
Paste this into your AI
My Stripe webhook fails with "No signatures found matching the expected signature for payload". Rewrite the handler so it verifies the signature using the raw request body for this framework, reads the signing secret from an environment variable, and returns 400 on failure and 200 on success.
Still broken?
In the Stripe dashboard, open the endpoint and look at a failed delivery. It shows exactly what your server replied.