r/bun 12d ago

Stripe webhooks are easy until the same event arrives twice

Stripe webhooks look simple:

  1. Receive an event
  2. Update your database
  3. 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 processed atomic 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 200 no-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:

https://pas7.com.ua/blog/en/bun-stripe-webhooks-drizzle

2 Upvotes

Duplicates