All posts
·6 min read

Stripe webhooks done right

Eight things every Stripe webhook handler should do, from signature verification to handling out-of-order events.

Stripewebhookspayments

Money flows through webhooks

If you run anything on Stripe, your webhook handler is the most important code in your app. Here is what we ship in every Stripe integration.

1. Verify the signature

Use stripe.Webhook.construct_event. Do not roll your own verifier.

event = stripe.Webhook.construct_event(
    payload, sig_header, webhook_secret
)

The secret comes from Stripe's dashboard. Store it as an environment variable.

2. Respond in under 5 seconds

Stripe retries on timeout. Acknowledge quickly, process in a worker.

3. Idempotency via event.id

Stripe will send the same event more than once. Insert event.id into a table with a unique constraint and exit early on duplicates.

4. Handle out-of-order delivery

Webhook events are not guaranteed in order. A payment_intent.succeeded can arrive before payment_intent.created. Do not rely on order.

Refetch the relevant object from the Stripe API if you need the current state:

intent = stripe.PaymentIntent.retrieve(event.data.object.id)
if intent.status == "succeeded":
    ...

This costs an extra API call but it is correct.

5. Process the events you care about

Subscribe to specific event types in the Stripe dashboard. Do not catch-all. You will receive dozens of types you do not handle, and a 200 OK on an unhandled event is fine but noisy.

The ones you almost certainly care about:

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • charge.refunded
  • customer.subscription.updated
  • customer.subscription.deleted
  • invoice.payment_failed

6. Reconcile periodically

Webhooks miss. Stripe is honest about this — they recommend a nightly reconciliation. Pull yesterday's events from the API and compare to your database. Surface diffs in a review queue.

7. Test in the dashboard

Stripe lets you send test webhooks to your endpoint from the dashboard. Use it during development; do not wait for real events to test.

8. Log everything

Every event, with its ID, type, processed/failed status, and a link to the Stripe dashboard. When finance asks "why did this customer get charged twice," you need the audit trail.

What we will not skip

We always build the Stripe handler with a DLQ and a reconciliation cron. Without them, you are gambling with the customer's money — and a payment problem is the worst kind of problem to debug.

Got a workflow problem?

Let's talk about whether n8n, a custom backend, or a hybrid fits your case.

A 30-minute discovery call. Free, honest, you leave with a written direction either way.

Start QuizBook a Call