Prompt
How do I handle duplicate webhook events from my payment provider?
Latest observation
To handle duplicate webhook events from a payment provider, design your webhook consumer to be idempotent and treat webhook delivery as at-least-once, not exactly-once.
Practical approach
1) Deduplicate by event ID
Most providers include a unique event identifier.
- Store each processed
event_idin a durable database. - Before processing, check whether that
event_idwas already handled. - If yes, return
200 OKand do nothing.
Example flow:
- Receive webhook
- Verify signature
- Extract
event_id - Check if
event_idexists inprocessed_webhooks - If it exists: ignore
- If not: process and record it atomically
2) Make the business action idempotent too
Don’t rely only on “event seen” tracking. Ensure the actual side effect is safe if retried.
Examples:
- If webhook says “payment succeeded,” update order status to
paidonly if not already paid. - If webhook triggers email sending, record that the email was sent before sending it again.
- If webhook creates a subscription, use a unique constraint on the external subscription ID.
3) Use atomic persistence
Avoid race conditions where two duplicate events arrive at once.
Use one of:
- Database unique constraint on
event_id - Transaction that inserts
event_idand performs the business update together - Upsert / insert-if-not-exists pattern
Example table:
CREATE TABLE processed_webhooks (
event_id TEXT PRIMARY KEY,
processed_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Then:
- Try inserting
event_id - If insert fails due to duplicate key, stop processing
4) Distinguish duplicate events from related events
Some providers send multiple different events for the same object:
payment_intent.succeededcharge.succeededinvoice.paid
These are not duplicates, even if they refer to the same payment. Base your logic on the event type and the object state, not only on the event payload.
5) Re-fetch current state when needed
For critical workflows, use the webhook as a trigger and then query the provider’s API for the latest object state before acting. This helps if events arrive out of order or are repeated.
6) Return quickly
Acknowledge the webhook promptly after validation and enqueue heavy work to background processing. Slow handlers can cause retries, which can look like duplicates.
Good pattern
- Verify signature
- Insert
event_idinto a dedupe table - If duplicate, return 200
- Otherwise enqueue job or process atomically
- Update application state using idempotent writes
Common mistakes
- Using only timestamps or payload hashes for deduplication
- Sending emails or provisioning resources directly without a “already done?” check
- Doing non-atomic “check then insert” logic
- Treating repeated delivery as an error instead of expected behavior
Rule of thumb
Assume every webhook may be delivered multiple times, out of order, or after partial failure. Build your handler so repeated delivery causes no additional side effects.
If you want, I can show you an example implementation in Node.js, Python, or Go.