Nguyen Hung Anh
All notes

2026-09-01

Making payment webhooks idempotent

Why providers retry, and how an idempotency key plus an outbox keeps your ledger correct.

Payment providers retry webhooks until they get a 2xx. If your handler times out after writing to the database, the next retry applies the same event again.

The fix

  1. Verify the signature before doing anything else.
  2. Insert the provider's event id into a processed_events table with a unique constraint — in the same transaction as the business change.
  3. If the insert conflicts, return 200 and stop.
insert into processed_events (provider, event_id) values ($1, $2)
on conflict do nothing
returning event_id;

No row returned means the event was already applied.