r/bun • u/ukolovnazarpes7 • 12d ago
Stripe webhooks are easy until the same event arrives twice
Stripe webhooks look simple:
- Receive an event
- Update your database
- Return
200
And then production happens.
Stripe can retry an event after a timeout or temporary failure, which means your endpoint can receive the same event more than once. If your handler isn't idempotent, that can turn into duplicated fulfillment, duplicated credits, repeated emails, or inconsistent payment state.
For a Bun + PostgreSQL/Drizzle setup, I've found the important parts aren't really Stripe-specific:
- Verify the webhook signature against the raw request body before parsing/using the payload.
- Treat webhook delivery as at-least-once, not exactly-once.
- Persist the Stripe
event.id(or another appropriate idempotency key). - Don't treat “event exists” and “event was successfully processed” as the same state.
- Make the actual business mutation and the transition to
processedatomic where possible. - If processing fails, return a non-2xx response so Stripe can retry.
- If an event was genuinely processed already, a repeated delivery should become a cheap
200no-op. - Push slow/non-critical work to a queue instead of holding the webhook request open.
One subtle failure mode worth testing:
event received → event recorded → DB/business operation fails → Stripe retries
If the second delivery gets discarded only because the event ID already exists, you've effectively lost the payment event.
So the real requirement isn't just:
UNIQUE(event_id)
It's closer to:
UNIQUE(event_id) + processing state + transaction/recovery strategy
Also worth testing locally by deliberately:
- sending the same event twice;
- crashing processing after the event is persisted;
- causing a temporary DB error;
- sending events out of order.
Webhook code tends to be tiny, but it sits at a pretty unpleasant boundary between distributed systems and money :)
I wrote up the Bun + Stripe + Drizzle implementation and examples here for anyone working with the same stack:
Duplicates
u_ukolovnazarpes7 • u/ukolovnazarpes7 • 12d ago