## TL;DR

Stripe redelivers webhooks until acknowledged, so a handler without an idempotency guard processes the same event twice: double fulfillment, double credits, double emails. Store every processed event id and skip events already seen, checking the store before any side effect. Make the guard the first line of the handler, not an afterthought.

```text
agent's webhook handler processed the same stripe event twice: no idempotency guard
```

## Use this when

- A Stripe webhook handler processed the same event twice
- You see duplicate side effects from webhooks
- Your handler lacks an event-id store

## Not for this skill when

- Two different events arrived (that is normal, not duplication)
- The webhook signature fails (verification problem)
- You are debugging event delivery delays

## Steps

1. Confirm duplication: find two handler executions with the same Stripe event id.
   Expected: The double-processing is proven.
2. Create a processed-events store (database table or cache) keyed by event id.
   Expected: The guard has a home.
3. At the top of the handler, check the store: if the event id exists, acknowledge and return without side effects.
   Expected: Duplicates are skipped.
4. Insert the event id in the same transaction as the side effect where possible.
   Expected: The check and the effect are atomic.
5. Replay a past event from the Stripe dashboard to prove the guard holds.
   Expected: Idempotent handling is verified.
6. Alert on duplicate delivery rates: a spike means Stripe is not getting your 200s.
   Expected: Delivery health is monitored.

## Tool compatibility

Stripe webhooks and event ids; any datastore for the processed-event record.

## Variant phrasings

### stripe webhook duplicate event handling

The general problem phrasing.

### stripe event idempotency guard

The fix as a phrase.

### webhook processed twice stripe

The symptom phrasing.

## Why it happens

Stripe's webhook delivery is at-least-once by design: if your endpoint is slow, errors, or returns a non-2xx, Stripe retries the same event, sometimes minutes later. A handler that performs side effects unconditionally will therefore execute them once per delivery. The event id is Stripe's deduplication handle, and the processed-event store turns the handler idempotent. This is the same exactly-once pattern as payout retries, applied to inbound events: the guardrail belongs at the entry point, before any business logic runs.

## Edge cases / pitfalls

- Return 200 only after the side effect is durably recorded, or Stripe will retry a completed handler.
- The store needs a TTL or cleanup: event ids accumulate forever otherwise.
- Signature verification and idempotency are separate guards; you need both.

## Provenance

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