All articlesPayments

Stripe Webhooks You Can Actually Audit

July 22, 20267 min read
Stripe Webhooks You Can Actually Audit

Stripe is very good at moving money. The hard part has never been the charge — it is making sure your own database agrees with Stripe about what happened, in the right order, exactly once.

Treat webhooks as facts, not triggers

A webhook is not an instruction to do something. It is a report that something already happened. Once you write handlers that way, most of the classic bugs disappear, because you stop trying to make the webhook drive your business logic and start letting it correct your record of the world.

Every event gets written to an events table first — raw payload, event id, type, received timestamp — before anything else runs. If the handler later crashes, the fact survives, and it can be replayed.

Idempotency is not optional

Stripe will send the same event more than once. That is documented behaviour, not an edge case. The event id is the natural idempotency key: a unique index on it turns a duplicate delivery into a cheap no-op instead of a second payout.

If replaying yesterday's webhooks would change your balances, you do not have idempotency — you have luck.

Reconcile on a schedule

Webhooks get missed. Endpoints go down, deploys drop requests, and networks do what networks do. A nightly job that pulls the day's charges from Stripe and diffs them against your own records is the difference between finding a gap yourself and having a customer find it for you.

When someone asks why a payout is what it is, I want to answer with a query, not a guess. That is the whole point of building it this way.