Guides · Free check · $9 fix pack

Guides

Is your Stripe webhook failing silently? Check it, alert on it, recover

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.

Why nobody notices

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).

1. Check that the live endpoint exists and is enabled

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": [...]

2. Look for failed deliveries

curl -G https://api.stripe.com/v1/events -u "$STRIPE_LIVE_KEY:" \
  -d delivery_success=false \
  -d "types[]=checkout.session.completed" \
  -d limit=10

3. Get told automatically: a daily check

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.

4. Fix the cause, then recover what you missed

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:

  1. Resend recent events. From the Dashboard you can resend an event "for up to 15 days after the event creation"; with the Stripe CLI, stripe events resend works "for up to 30 days after the event creation" (source).
  2. Or process the backlog yourself. List events with 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.
  3. Older than 30 days? Events are gone from the API, so rebuild from the objects: list Checkout Sessions or subscriptions and sync each one (see subscription status not updating).
Check yours in 10 seconds (free).

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 check

FAQ

How do I know if my Stripe webhook is failing?

Open 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.

Does Stripe alert me when my webhook fails?

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.

How long does Stripe keep retrying a failed webhook?

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.

Can I recover webhook events from more than 30 days ago?

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.

Sources

More guides

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.