
Playbook3 sections · 2 min readBy Dezső Mező · Published October 3, 2026.mdStripeWebhooksPaymentsIdempotency
Payment webhooks are the one place 'it worked in testing' is actively misleading: test events arrive once, in order, while production delivers duplicates, reorderings and delays as a matter of course. The patterns below are the difference between a checkout that occasionally double-ships and one where 'the same event twice' is a non-event.
01Verify the signature before anything else
Stripe signs every delivery; verify it against the endpoint secret before parsing or logging as trusted. An unverified webhook is a public POST endpoint that claims money moved — which is exactly what it is without the check.
02Store the event id, process once
The event id is your idempotency key: insert it before processing and let the unique constraint make the second delivery a no-op. Same for the fulfillment it triggers — order created, email queued, entitlement granted each need their own 'already done' record.
03Read state, not the event payload
The event says something changed; the object fetch says what it is now. Handling 'checkout.session.completed' by reading the current session — rather than trusting the payload — is immune to reordering, because late events still fetch the latest truth.
What to take away
- Verify the signature — the webhook is a public endpoint.
- Event id is the idempotency key; dedupe before you act.
- Fetch current state; the payload is stale on arrival.
- Duplicates and reordering are normal, not edge cases.
More from the lab
Browse all entries
What custom software actually costs
Custom software or off the shelf — the honest test
The first ninety days of a retainer, as it actually goes
Why clients get a progress page instead of status emails
Want this looked at on your own system?Start a conversation
