Skip to content
newThe webhook.co MCP server is live — turn any webhook into an agent event. Read the docs
test locally

Test Stripe webhooks locally

A sandbox event and a live event travel different retry curves, which makes "it worked when I tested it" a weaker statement than it sounds. Getting a real Stripe event onto the laptop where the handler runs is the part worth wiring up carefully.

The loop

Create an endpoint and it prints a permanent ingest URL. Everything sent there is captured whole — headers, body, the exact bytes — before anything else happens. Stream those events to a port on your machine, and replay any one of them as often as you like while you fix the handler. Forwarding runs from your own machine, so it isn't a billable delivery.

wbhkstripe
$ wbhk endpoints create stripe-dev
https://wbhk.my/whep_…
$ wbhk listen <endpoint-id> --forward http://localhost:3000/webhooks
$ wbhk replay <event-id> --forward http://localhost:3000/webhooks

What's different about Stripe

Stripe's event names are dotted strings that look easy to guess until they bite. A sub-resource gets its own segment, and it does not cascade: customer.subscription.updated fires nothing on customer.updated, so a destination subscribed to the parent sits silent while subscriptions churn. The second surprise is that retry behavior differs across the two halves of your test loop. An event created in a sandbox is retried three times over the course of a few hours; live mode backs off across three days. Tune a handler's failure path against sandbox timings and you have tuned it against the wrong curve.

Where the URL goes

Stripe keeps event destinations in Workbench, under the Webhooks tab. The button that starts a new one reads "Create an event destination" on Stripe's webhooks page and "Create new destination" inside Workbench itself. Two screens come before the interesting ones: which account the destination listens to, and which API version its event objects are rendered against. Then pick your event types, choose Webhook endpoint as the destination type, and drop the ingest URL into the Endpoint URL field. Registered endpoints have to be publicly accessible HTTPS URLs, and Stripe's webhook traffic speaks only TLS 1.2 and 1.3. You can register up to 16 webhook endpoints with Stripe, so prune dead ones before adding another. The same registration is available through the API.

Stripe's own CLI does the firing. Running stripe trigger payment_intent.succeeded fires a real test event rather than a canned sample. The other documented route is re-delivery rather than creation: Resend in the Dashboard, or stripe events resend from the CLI, both acting on an event that already exists. A Dashboard button for sending a fresh test event is not something Stripe's pages document.

What Stripe sends

Stripe names events resource.event: dot-separated, lowercase, with underscores living inside a segment rather than between them. A sub-resource earns an extra segment. The shape is regular enough to invite guessing, and the sub-resource rule is where guessing fails — the parent resource stays quiet when a sub-resource changes.

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • checkout.session.completed
  • charge.succeeded
  • invoice.paid
  • customer.subscription.deleted

Worth knowing first

Sandbox retries give up early

Live mode retries a failing destination with exponential backoff for up to three days. An event created in a sandbox gets three attempts across a few hours and then stops, so the retry pressure you observe while testing is far shorter than what a broken production handler will actually sit through.

A redirect is recorded as an error

Stripe doesn't follow a 3xx on a webhook request — it logs the delivery as a failure, and tells you to register the URL the redirect resolves to. The rule that sits next to it on the same page: return the 2xx before any complex logic that could cause a timeout. An event showing a status code of 200 is what marks the delivery as successful.

Ordering isn't promised, repeats happen

Stripe states plainly that events aren't delivered in the order they were generated, and that an endpoint might occasionally receive the same event more than once. The documented defense is to make the destination independent of order, log the event IDs you have processed, and skip anything already logged.

Five minutes of clock tolerance

Stripe's signature libraries default to a five-minute tolerance between the Stripe-Signature timestamp and the current time, so a machine with a drifted clock will reject perfectly good payloads. Worth ruling out before you go hunting for a wrong signing secret.

Signature checking is handled for you once the secret is registered — you can also check a captured signature by hand in the signature verifier.

Stripe webhooks: common questions

Does Stripe verify a webhook endpoint before it starts sending events?

Stripe documents no echo token and no GET challenge at registration. The verification it does document is HMAC signature checking on the Stripe-Signature header, which happens on every delivery rather than once at setup. The endpoint has to be a reachable, public HTTPS URL, and nothing more.

How long after creation can a Stripe event be resent?

The Dashboard Resend action on a specific event works for up to 15 days after that event was created. The Stripe CLI resend command stretches the same idea to 30 days. Stripe documents no re-delivery path past those windows, so capture what you need while the event is fresh.

Why doesn't customer.updated fire when a Stripe subscription changes?

Because Stripe treats a subscription as a sub-resource with its own slice of the event namespace. Changes surface as customer.subscription.updated and do not trigger a corresponding event for the parent resource. A destination subscribed only to the parent will never see subscription activity at all.

Point a webhook at it. Watch it land.

Start free: a permanent URL, full inspection, one-command replay — and outbound delivery, because every feature is on every plan. Move up when you need more events.