Duplicate webhooks: how do you process an event only once?
Design an idempotent workflow so repeated webhook deliveries produce one business effect, even under concurrent processing and retries.

You cannot guarantee that a webhook will be delivered exactly once. Timeouts, lost connections, slow responses, automatic retries, and manual redelivery can all send the same event more than once. The correct solution is an idempotent workflow: repeated deliveries are accepted, but only one execution may create the business effect, such as an order, email, payment update, or CRM record.
A safe flow is: verify the signature → derive a stable idempotency key → atomically claim the event in a store protected by a unique constraint → allow only the winner to process → record state and result → handle every redelivery safely. Do not rely on a search node followed by a create node. Two concurrent executions can both observe “not found” and create duplicate records.
Cause
Duplicate webhooks are normal in distributed systems
Retries reduce event loss, but the receiver must control repeated effects.
A provider that does not receive a 2xx response may treat a delivery as failed even though the workflow has started. A harder case occurs when the downstream action succeeds but the connection fails before the result is recorded. The sender retries, while the receiver cannot tell whether the previous attempt finished. Stripe explicitly warns that an endpoint may receive the same event more than once, and GitHub redelivery preserves the same X-GitHub-Delivery value.
The production goal should therefore not be described as exactly-once delivery. A realistic model is at-least-once delivery with an effectively-once business effect. The workflow may be triggered repeatedly, but the database, destination API, and operation ledger ensure that an order is created once. Do not assume ordered delivery either. A late older event must not overwrite a newer object state.




