Stripe sent the same checkout.session.completed event to our webhook four times in a row. Each delivery was a real HTTP request from Stripe's infrastructure, each signed correctly, each with the same evt_* id.

If our fulfillment handler had treated each one as a new purchase, we'd have sent the buyer four license emails, granted them four GitHub repo invites, and credited four sales in PostHog. We didn't, because our handler is idempotent. This is the post we wish we'd read before writing that handler.

Why duplicates happen

Stripe's webhook delivery is at-least-once, not exactly-once. The docs are direct about it: "Webhook endpoints may receive duplicate events." It is not a bug. It is the contract.

You get duplicates when: