Payment providers retry webhook delivery on any ambiguous response — a timeout, a 500, even a slow 200. That means the handler will, eventually, receive the same event twice, and a naive implementation will charge or credit the account twice.
The fix is small, but has to be there from day one
def handle_payment_webhook(event: dict): event_id = event["id"] if already_processed(event_id): return {"status": "duplicate_ignored"} with db.transaction(): record_event(event_id) apply_payment(event) return {"status": "ok"}
An idempotency key checked inside the same transaction as the side effect — but it has to be there from the first webhook handler written, not added after the first duplicate charge is reported.