# Stripe webhooks: expect duplicates and disorder
## The facts
- Stripe retries event delivery until your endpoint returns 2xx. A slow handler gets the same event again.
- Delivery order is not guaranteed. `invoice.finalized` can arrive before the `customer.subscription.updated` that logically preceded it.
- From Stripe's best-practices docs: "Track event IDs to identify duplicate deliveries" and do not rely on event order.
## The handler shape
1. Verify the signature first (see the signature skill). Unverified events are dropped before any logic.
2. Check a processed-events store for `event.id`. If seen, return 200 immediately. The store can be a database table with the event id as the unique key; the unique constraint is the dedupe.
3. Only then switch on `event.type` and apply the business logic.
4. Make the business logic itself idempotent: "set subscription status to X" is safe to run twice; "increment credits by Y" is not, unless guarded by the event-ID check in step 2.
5. Return 200 quickly. Do slow work (emails, provisioning) asynchronously after acknowledging.
## Ordering
Never assume event A arrived before event B. Design handlers to be order-independent:
- Derive state from the object in the event (`event.data.object`), not from the sequence of events.
- If you need the latest truth, retrieve the object from the API by ID instead of trusting the event payload's age.
## Backfilling
Missed events happen. Stripe keeps events retrievable via the API for 30 days. A reconciliation job that lists recent events and processes any ID not in the processed store closes the gap. Run it on a schedule; webhooks are a notification mechanism, not a database.
## Checklist
- Processed event IDs persisted with a uniqueness guarantee.
- No handler logic depends on arrival order.
- Signature verified before dedupe check (cheap rejects first).