## TL;DR
Duplicate dispute webhooks happen when a provider retries a slow endpoint or when the same URL is registered twice. Dedupe on your side by event id in a persistent store, return 200 fast, and check the dashboard for doubly-registered endpoints.

```text
duplicate dispute webhooks firing twice: how to fix
```

## Use this when
- The same dispute event arrives twice and your ledger shows double entries.
- Duplicates appear on every event, not just one.
- The second copy arrives seconds or minutes after the first.

## Not for this skill when
- Events never arrive. That is a subscription problem.
- Events arrive but in the wrong order. That is an ordering problem.

## Steps
1. Log every incoming event id with its arrival timestamp. Verify: you can see pairs of identical ids in the log.
2. Add an event-id dedupe store (a table or cache with a unique key) and skip any id already processed. Verify: a replayed event is acknowledged but not reprocessed.
3. Make your handler acknowledge quickly and do heavy work asynchronously. Verify: the endpoint responds 200 well under the provider's timeout.
4. Check the dashboard for the same URL registered twice or overlapping event subscriptions. Verify: each event type has exactly one live endpoint.
5. Backfill your ledger by collapsing rows with duplicate event ids, keeping the earliest. Verify: totals match the provider's dispute list.

## Variant phrasings
### dispute webhook delivered twice
Common phrasing.
### stripe webhook duplicate dispute event
Provider-specific search.
### webhook event id dedupe disputes
Solution-wording search.

Compatibility: Any webhook provider that retries on slow or failed acknowledgements. Event ids are unique per event across Stripe, Adyen, and similar providers.

## Why it happens
Providers retry when your endpoint is slow, errors, or sits behind a redirect. Each retry is a full redelivery. Separately, teams often register the same URL twice during migrations and each registration gets its own copy. Both look identical from your side.

## Edge cases / pitfalls
- Dedupe on event id, not on payload hash. Retries can carry slightly different timestamps.
- Keep dedupe records for at least the provider's retry window, longer if you replay events.
- A 200 after partial processing is worse than a 500. Acknowledge only when the event is safely stored.
- During incident replays, expect duplicates by design. Keep the dedupe on.

## Provenance

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