stripe listen works perfectly in dev. Production returns errors or silently drops events. The gap between local and production Stripe webhooks is almost always one of four things — and you can check all of them in under 10 minutes.
Stripe CLI's stripe listen creates a temporary local tunnel — it does NOT register a production webhook. You must go to Stripe Dashboard > Developers > Webhooks > Add endpoint and add your production URL manually. If there's no endpoint registered for your production URL, Stripe never sends events there.
Stripe CLI gives you a temporary signing secret (whsec_test...) for local development. Your production endpoint has a different signing secret shown in the Dashboard. If your STRIPE_WEBHOOK_SECRET env variable in production still has the local CLI secret, signature verification fails on every event.
Local Express servers often have consistent middleware ordering. Serverless environments (Vercel, Netlify, AWS Lambda) may apply different middleware or parsing depending on the runtime. Explicitly configure raw body parsing in your production handler — don't rely on framework defaults.
Stripe only sends webhooks to HTTPS endpoints in production. Your local stripe listen tunnel provides HTTPS. If your production URL is HTTP (non-SSL), Stripe rejects the endpoint registration entirely. Ensure your production domain has a valid SSL certificate.
Your Stripe Dashboard webhook endpoint configuration specifies which events to send. If you registered the endpoint with only payment_intent.created but your code handles payment_intent.succeeded, production events never arrive. Check the endpoint's event selection and add all required event types.
Real operator. No ticket queue. San Diego-based. Most issues resolved in one thread.
Related problems in this cluster: