Skip to content
← All posts
Engineering·7 min read

Designing idempotent webhook consumers

DO

Daniel Okafor

Engineering, HookFlow

No webhook provider guarantees exactly-once delivery, and HookFlow doesn't either. Networks drop acknowledgements, load balancers time out, and a provider that never heard your 200 will send the event again. The only robust answer is a consumer that can safely process the same event twice.

The simplest pattern is an idempotency table keyed by event ID. Before doing any work, insert the ID inside the same database transaction as your side effects. If the insert violates a unique constraint, you have already handled this event and can return 200 immediately. Every event HookFlow forwards carries a stable id, even across retries and replays, precisely so this works.

Side effects outside your database need more care. If handling an event sends an email or calls a payment API, pass the event ID through as that system's idempotency key. Stripe, Adyen and most modern APIs accept one; for those that don't, record the outbound call in your own table first and reconcile on retry.

Ordering is the second trap. Providers rarely promise that subscription.updated arrives after subscription.created. Instead of trusting arrival order, compare a version or timestamp on the resource and ignore events older than what you have stored. HookFlow exposes the provider's original timestamp in the X-HookFlow-Original-Timestamp header for exactly this comparison.

Finally, respond fast. Acknowledge within a few seconds and move heavy work to a queue. A handler that takes 25 seconds to respond will eventually time out, get retried, and run twice concurrently — which is where idempotency bugs hide.