## TL;DR
Ticket webhooks deliver at-least-once: retries, redelivery, and multi-subscriber fan-out all produce duplicates. Key every consumer on the event id, record processed ids durably, and make the action itself idempotent so a duplicate that slips through is harmless. Acknowledge fast and process async so your own slowness does not manufacture retries.

## The query

```text
agent's webhook consumer double-processing ticket events: idempotency
```

## Use this when

- Webhook handlers apply ticket changes twice
- Duplicate side effects appear (double comments, double updates)
- You are building a ticket webhook consumer
- Retries cause duplicate processing

## Not for

- Setting up webhook subscriptions
- Signature verification
- Choosing webhook event types
- Webhook payload schema design

## Steps

### 1. Dedupe on the event id at the door

Maintain a durable store of processed event ids with a generous TTL. Check it first on every delivery; duplicates get acknowledged and dropped. This handles retry-driven duplicates completely.

Expected output: retry duplicates acknowledged without reprocessing.

### 2. Make the action idempotent, not just the gate

Dedupe gates can fail (store eviction, race conditions). Design the action so double-execution is safe: upsert instead of create, set state instead of toggling, check-then-act inside a transaction. Defense in depth.

Expected output: actions safe under double execution.

### 3. Acknowledge fast, process asynchronously

Return success immediately and queue the event. Slow endpoints trigger sender retries, which are the largest source of duplicates. The queue plus the dedupe store makes retries free.

Expected output: endpoint latency in milliseconds, processing off-request.

### 4. Handle the race: two deliveries in flight at once

Two copies of the same event can arrive concurrently. Use atomic check-and-set on the event id, or a database uniqueness constraint, so only one copy processes. A check-then-insert without atomicity still double-processes under concurrency.

Expected output: concurrent duplicates collapsed to one processing.

### 5. Monitor duplicate rate as a health signal

Some duplicates are normal. A spike means the endpoint is slow or failing and the sender is retrying. Alert on the duplicate ratio to catch endpoint degradation early.

Expected output: duplicate-rate dashboard and alert.

## Template: the consumer skeleton

```text
On ticket webhook event:
  atomic check-and-set on event id; if seen, ack and drop
  ack immediately; queue for processing

Worker:
  re-check event id (belt and suspenders)
  apply action idempotently (upsert, set, transactional check-then-act)
  record event id durably
```

## Variant phrasings

### webhook processing same event twice

Steps 1 and 4. Dedupe plus atomicity.

### duplicate ticket comments from webhook

Step 2. The action must be idempotent.

### webhook retry causing duplicates

Steps 1 and 3. Dedupe store plus fast ack.

## Why it happens

Webhook senders retry on any uncertainty: timeouts, 5xx, slow responses. Each retry is a full duplicate delivery by design. Consumers that treat delivery as exactly-once corrupt state on the first retry storm. At-least-once is the contract; idempotency is the consumer's half of it.

## Edge cases

- Event ids across providers differ in stability: some reuse ids on redelivery (good), some mint new ones (dedupe on business key instead).
- Out-of-order plus duplicate: combine id dedupe with timestamp-based stale-event rejection.
- Poison events: an event that crashes the worker gets retried forever. Dead-letter after N failures.
- Dedupe store sizing: TTL must exceed the maximum retry window, or old duplicates slip through.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_eQoQdiwvOLLuRY9JmMz6rw
