## 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

```text
intercom webhook events arriving duplicated or out of order
```

## Use 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

```text
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 object
```

## Variant 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
