Fix guides / Stripe: "No signatures found matching the expected signature"
Fixing Stripe's "No signatures found matching the expected signature for payload"
Payments go through, but your webhook handler throws Webhook signature verification failed: No signatures found matching the expected signature for payload. Stripe's check needs three things to be exactly right. One of them is not.
What the check needs
stripe.webhooks.constructEvent(body, signature, secret) needs: the raw request body exactly as Stripe sent it, the Stripe-Signature header, and the signing secret for that specific endpoint. If any one is off, you get this error.
Cause 1: the wrong signing secret (most common)
- The secret printed by
stripe listenon your laptop is different from the secret of the endpoint in your Stripe Dashboard. Production must use the Dashboard one. - Test mode and live mode endpoints have different secrets. If you just switched to live mode, create the live endpoint and copy its secret.
- Find it in the Stripe Dashboard under Developers, then Webhooks (Workbench), then your endpoint, then Reveal secret. It starts with
whsec_. - After changing the env var on your host, redeploy.
Cause 2: the body was parsed before you checked it
If your framework turns the body into JSON first, the bytes change and the signature no longer matches.
Next.js App Router: read the body as text.
export async function POST(req) {
const body = await req.text();
const sig = req.headers.get('stripe-signature');
let event;
try {
event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return new Response('Bad signature', { status: 400 });
}
// handle event.type ...
return Response.json({ received: true });
}
Express: use the raw body for the webhook route, and register it before express.json().
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], secret);
res.json({ received: true });
});
app.use(express.json()); // other routes after
Supabase Edge Functions (Deno): use the async version with the Web Crypto provider.
const cryptoProvider = Stripe.createSubtleCryptoProvider();
const body = await req.text();
const event = await stripe.webhooks.constructEventAsync(
body, req.headers.get('Stripe-Signature'), Deno.env.get('STRIPE_WEBHOOK_SECRET'),
undefined, cryptoProvider
);
Also deploy the function with JWT verification off (supabase functions deploy stripe-webhook --no-verify-jwt), because Stripe does not send a Supabase login token. Your signature check is what protects the endpoint.
How to verify
In the Stripe Dashboard, open your endpoint, find a failed event, and click Resend. A 200 response means it is fixed.
Request a fix
Last updated 2026-10-19.