intercom webhook events arriving duplicated or out of order
A design guide for handling duplicate and out-of-order Intercom webhook events, in the business-apis-integrations category: idempotent consumers keyed on event ids, and state machines that tolerate reordering. Use when webhook handlers double-apply changes, or when event ordering assumptions break. Not for webhook endpoint setup, signature verification, or notification preferences.
TL;DR
Intercom webhooks are delivered at-least-once with no ordering guarantee, so your consumer must be idempotent and order-tolerant by design. Dedupe on the event id, store the latest event timestamp per object, and ignore stale events rather than applying them. If your handler assumes each event arrives exactly once and in order, duplicates and reordering will corrupt state.
The query
intercom webhook events arriving duplicated or out of orderUse this when
- Webhook handlers apply the same change twice
- State ends up wrong after bursts of events
- Conversation or contact updates arrive out of sequence
- You are building the receiving end of Intercom webhooks
Not for
- Setting up webhook topics or endpoints
- Signature verification failures
- Choosing which events to subscribe to
- Email notification configuration
Steps
1. Dedupe on the event id before doing anything
Keep a store of processed event ids with a TTL of at least a few days. When an event arrives, check the id first; if seen, acknowledge and stop. This single step kills the duplicate problem.
Expected output: duplicate deliveries acknowledged without reprocessing.
2. Track the latest event timestamp per object
Store the newest event timestamp seen for each conversation or contact. When an event arrives with an older timestamp than what you have, skip applying it. Ordering then becomes "last writer wins by event time" instead of "by arrival time."
Expected output: stale events ignored, object state reflects the newest event.
3. Make every handler action idempotent
Design each action so running it twice has the same effect as once: upserts instead of creates, state sets instead of increments. Retries and duplicates then become harmless.
Expected output: handler logic reviewed, non-idempotent actions rewritten.
4. Acknowledge fast, process async
Return 200 immediately and queue the event for processing. Slow handlers trigger Intercom retries, which manufacture the very duplicates you are fighting. The queue plus dedupe makes retries free.
Expected output: endpoint responds in milliseconds, processing happens off-request.
5. Alert on duplicate rate spikes
A low background duplicate rate is normal. A spike means your endpoint is slow or erroring and Intercom is retrying. Monitor the duplicate ratio as a health signal for the endpoint itself.
Expected output: a dashboard and alert on duplicate ratio.
Template: the consumer skeleton
On webhook event:
if event id in processed store: return 200
queue the event; return 200 immediately
Worker:
if event id in processed store: drop
if event timestamp < stored latest for object: drop as stale
apply action idempotently (upsert, not create)
record event id; update latest timestamp for objectVariant phrasings
intercom webhook duplicate events
Steps 1 and 4. Dedupe plus fast ack.
webhook events out of order intercom
Step 2. Timestamp-based last-writer-wins.
idempotent intercom webhook handler
Steps 1 through 3 as the design pattern.
Why it happens
Webhooks ride on HTTP, which fails in ways that force retries, and Intercom makes no ordering promise across events. Duplicates come from retries after slow or failed acknowledgments; reordering comes from parallel delivery and redelivery paths. The platform is behaving as documented; the consumer has to be built for it.
Edge cases
- Event ids across redelivery: retries carry the same event id, so id-keyed dedupe catches them. New events for the same object carry new ids.
- Clock skew on timestamps: allow a small grace window before calling an event stale.
- Processed-store eviction: a TTL shorter than the retry window lets old duplicates through. Size the TTL generously.
- Bulk historical replays: a replay floods the dedupe store. Handle replays as a separate batch path with the consumer paused.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_LjDN9ZA-nWSCIKLjHhpkuA