The Node library's error reads "No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?" There are three usual causes.
"Stripe requires the raw body of the request to perform signature verification... Any manipulation to the raw body of the request causes the verification to fail." (Stripe docs). Re-serialising JSON changes whitespace and key order, so the signature no longer matches.
await request.text() and pass that string to constructEvent. Don't call request.json() first.export const config = { api: { bodyParser: false } }) and read the raw buffer.express.raw({ type: 'application/json' }) on the webhook route, before any global express.json().await req.text() and constructEventAsync with Stripe.createSubtleCryptoProvider(), as in Supabase's example (Supabase docs).Each endpoint has its own whsec_ secret, and "if you use the same endpoint for both test and live API keys, the secret is different for each one" (source). The Stripe CLI's stripe listen also prints its own secret for forwarded events (Stripe CLI docs), which won't verify real Dashboard deliveries. See test vs live secrets.
Stripe's libraries reject events whose signed timestamp is more than 5 minutes from the current time by default (source). A badly skewed server clock causes this; replaying old payloads by hand does too.
Turning verification off makes the error go away and lets anyone who knows the URL post a fake checkout.session.completed. See can someone fake a payment?
Paste your webhook URL. WebhookProof sends 3 harmless test requests and tells you whether Stripe can reach it and whether it rejects unsigned events.
Run the free checkStripe could not verify the event. The usual causes are a body that was parsed or modified before verification, or the wrong whsec_ signing secret for that endpoint and mode.
No. Stripe says the secret is different for test and live, and each endpoint has its own secret. The Stripe CLI also prints its own secret for forwarded events.
Stripe's libraries default to 5 minutes between the signed timestamp and the current time.
Stripe webhook not working? Check these 7 things in order · A customer paid but didn't get access. What went wrong? · Stripe webhook getting a 307 or 308 redirect · Stripe webhook on Supabase Edge Functions: fixing 401s and signature errors
Last reviewed 2026-10-18. WebhookProof is an independent tool, not affiliated with Stripe, Inc. Quotes are from the linked documentation.