No signatures found matching the expected signature
appears when:After the app is deployed to Vercel and a live charge fails to update the database
Stripe webhook not firing
Dashboard shows no delivery or a red 'failed' badge. Cause is almost always the raw-body parse, a test-mode secret in production, or an endpoint URL still pointing at localhost.
STRIPE_WEBHOOK_SECRET missing in production env, or the Next.js route parsed JSON before signature verification. Use await request.text(), pass the raw string to stripe.webhooks.constructEvent, and confirm the live-mode endpoint is registered in the Stripe dashboard. Return 200 in under a second.Quick fix for Stripe webhook not firing
01// app/api/stripe/webhook/route.ts02import Stripe from "stripe";03import { NextRequest } from "next/server";04 05const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);06const secret = process.env.STRIPE_WEBHOOK_SECRET!;07 08export async function POST(req: NextRequest) {09 const body = await req.text(); // raw string, NOT .json()10 const sig = req.headers.get("stripe-signature")!;11 12 let event: Stripe.Event;13 try {14 event = stripe.webhooks.constructEvent(body, sig, secret);15 } catch (err) {16 return new Response(`Webhook Error: ${(err as Error).message}`, { status: 400 });17 }18 19 // Acknowledge fast, process async20 queueEvent(event); // push to Inngest / QStash / your queue21 return new Response("ok", { status: 200 });22}Deeper fixes when the quick fix fails
01 · Verify the endpoint URL is registered in live mode
Stripe dashboards are toggled between test and live. A webhook created while the toggle was on Test will not receive live events. Switch the toggle to Live mode → Developers → Webhooks → Add endpoint. Use the exact production URL. Copy the new whsec_ secret into Vercel and redeploy.
02 · Drop edge runtime from the webhook route
01// ❌ DO NOT USE edge runtime for Stripe webhooks02// export const runtime = "edge";03 04// Stay on the default Node.js runtime so the Stripe SDK works05// (no directive needed)03 · Add idempotency keyed on event.id
Stripe replays events on retry. Without an idempotency check your database sees the same subscription twice. Store event.id in a Postgres table with a unique constraint; short-circuit if the row already exists.
01const { error } = await supabase02 .from("stripe_events")03 .insert({ id: event.id })04 .select()05 .single();06 07if (error?.code === "23505") {08 // already processed09 return new Response("duplicate", { status: 200 });10}Why AI-built apps hit Stripe webhook not firing
AI builders treat Stripe as a scaffolding checklist — one route handler, one test charge, one success toast. The shape works in preview because every preview hits the test-mode webhook that was registered during the onboarding wizard. The bug is silent because test-mode traffic never reveals the live-mode gap.
Vercel compounds the issue. Environment variables are scoped per environment, and NEXT_PUBLIC_ values bake into the bundle at build time. A founder who updates Preview but not Production ships a half-configured integration. Stripe Node SDK requires the Node runtime, so an inherited export const runtime = "edge" on the webhook route also breaks signature verification.
The fix pattern is always the same: register the live endpoint, scope the secret to Production, read the body raw, acknowledge in under a second, and enqueue the real work. Deploy once with that shape and Stripe webhook not firing stops recurring.
Stripe webhook not firing by AI builder
How often each AI builder ships this error and the pattern that produces it.
| Builder | Frequency | Pattern |
|---|---|---|
| Lovable | Every Stripe scaffold | Calls request.json() before verify |
| Bolt.new | Every Stripe scaffold | Edge runtime default breaks Node crypto |
| v0 | Common | Ships test-mode secret, never creates live endpoint |
| Cursor | Sometimes | Generates Express-style middleware that strips body |
| Replit Agent | Common | Omits STRIPE_WEBHOOK_SECRET from deploy checklist |
| Base44 | Rare | Swallows verification error with generic 500 |
Related errors we fix
Stop Stripe webhook not firing recurring in AI-built apps
- →Add a zod schema on process.env that fails the build when STRIPE_WEBHOOK_SECRET is missing.
- →Write an integration test that replays a signed event and asserts a 200 response in under a second.
- →Scope test keys to Preview/Development only — Production should contain live keys exclusively.
- →Add a Stripe CLI step to CI: `stripe trigger checkout.session.completed` against preview deploys.
- →Log the first 8 characters of the signature secret on boot so a misconfigured deploy is visible in logs.
Still stuck with Stripe webhook not firing?
Stripe webhook not firing questions
Why is my Stripe webhook not firing in production?+
How do I know if Stripe is even trying to send the webhook?+
What does 'No signatures found matching the expected signature' mean?+
Do I need separate Stripe webhook secrets for test and live?+
Can a Vercel cold start cause the webhook to fail?+
Ship the fix. Keep the fix.
Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.
Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.