Payments keep succeeding in Stripe while your app never hears about them. Nobody notices until a customer writes in, or a demo breaks. Here's how to see it, how to get told about it, and how to recover what you missed.
A failed delivery doesn't fail the payment. Stripe keeps retrying in the background: in live mode "for up to three days with an exponential back off", and in a sandbox "three times over the course of a few hours" (Stripe docs: Receive events). Your checkout still works, so the only symptom is on your side: an order that never unlocks, a plan that never updates.
Two failures are quieter still. If the endpoint was never created in live mode, Stripe never attempts a delivery, so there is no failure to see. And if a destination is disabled, "Stripe won't attempt to resend any events generated while the destination is disabled" (Stripe docs: Manage event destinations).
List your webhook endpoints with your live secret key (Stripe API: List webhook endpoints). Each one has a url, its enabled_events, and a status that "can be enabled or disabled" (Stripe API: Webhook Endpoint object). Confirm three things: the URL is your production URL (not localhost, a preview URL or an old domain), the status is enabled, and the events you fulfill on are in the list. Test and live endpoints are separate, with separate signing secrets; see test vs live webhook secret.
curl -G https://api.stripe.com/v1/webhook_endpoints -u "$STRIPE_LIVE_KEY:"
# For each endpoint: "url", "status": "enabled", "enabled_events": [...]
delivery_success=false, which returns "only events that failed delivery to at least one of your endpoints". Stripe only returns events from the last 30 days (Stripe docs: Process undelivered events, Stripe API: List events).curl -G https://api.stripe.com/v1/events -u "$STRIPE_LIVE_KEY:" \
-d delivery_success=false \
-d "types[]=checkout.session.completed" \
-d limit=10
Stripe's health alerts don't cover this for most accounts. The basic tier, which every user gets, covers 402 and 500 error alerts; webhook latency alerts are among the more detailed alerts on the advanced tier, which needs a Growth, Premium or Enterprise support plan (Stripe docs: Health alerts). So run your own check once a day from a cron job, using a restricted key that can only read events and webhook endpoints:
// daily-webhook-check.mjs (Node 18+, stripe npm package)
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_READ_KEY);
const problems = [];
const eps = await stripe.webhookEndpoints.list({ limit: 100 });
if (!eps.data.some((e) => e.status === 'enabled' && e.url.startsWith('https://yourapp.com/')))
problems.push('no enabled live endpoint for yourapp.com');
const since = Math.floor(Date.now() / 1000) - 24 * 3600;
const failed = await stripe.events.list({ delivery_success: false, created: { gte: since }, limit: 100 });
if (failed.data.length) problems.push(failed.data.length + ' events failed delivery in the last 24h');
if (problems.length) await notifyYourself(problems); // email, Slack, anything you read
This won't catch an endpoint that answers 200 without doing anything. For that, compare paid Checkout Sessions with unlocked orders in your own database.
The delivery log's status code tells you the cause: a redirect, a login wall, a signature error or a timeout. Work through the 7-step checklist. Then:
stripe events resend works "for up to 30 days after the event creation" (source).delivery_success=false for your event types and run each one through your handler (source). Make the handler idempotent first, since Stripe keeps retrying in the background and a resend "doesn't cancel Stripe's automatic retry behavior" (source). See duplicate Stripe webhook events.Paste your webhook URL. WebhookProof sends 3 harmless test requests and tells you whether Stripe can reach it and whether it rejects unsigned events. If it finds a problem, the $9 fix pack gives you the exact copy-paste fix for your stack (refundable for 14 days).
Run the free checkOpen the endpoint's Event deliveries tab in Workbench, or call the List Events API with delivery_success=false, which returns events that failed to reach at least one of your endpoints in the last 30 days. Also confirm the live endpoint exists and its status is enabled.
Not on every account. Stripe's basic health alerts cover 402 and 500 errors; webhook latency alerts are part of the advanced tier for Growth, Premium or Enterprise support plans. A daily script that checks delivery_success=false works on any account.
In live mode, up to three days with exponential backoff; in a sandbox, three times over a few hours. If the destination is disabled, Stripe does not resend events created while it was disabled.
Not from the Events API, which only returns the last 30 days. Rebuild state from the underlying objects instead, such as listing Checkout Sessions or subscriptions and syncing each one.
Stripe webhook not working? Check these 7 things in order · A customer paid but didn't get access. What went wrong? · No signatures found matching the expected signature for payload · Stripe webhook failing with a 307 or 308 redirect
Last reviewed 2026-10-21. WebhookProof is an independent tool, not affiliated with Stripe, Inc.. Quotes are from the linked documentation.