Skip to main content
Webhook delivery is at-least-once. Your handler will occasionally see the same delivery twice — a network blip after your handler completed, a retry that crossed paths with your 2xx, a manual replay during debugging. Make duplicates a no-op.
This page is about delivery-side idempotency: deduping repeats of the same webhook event on your server. For request-side idempotency — retrying a POST to the Awardee API safely with Idempotency-Key — see Idempotency. Same idea, different direction.

The dedupe key

Every delivery carries a unique id in the X-Awardee-Delivery header. The same id is reused across retry attempts of the same event — so seeing it twice means the same logical event, and you should skip the work. A different id, even for what looks like “the same” payload, is a different event (likely two genuine state changes in quick succession). Process it.

The pattern

Store seen delivery ids in a small table. Insert with ON CONFLICT DO NOTHING on receipt. If no row was inserted, skip the rest of the handler.
Node + Postgres
FastAPI + asyncpg
Insert the dedupe row in the same transaction as the side effects of processing the event. Otherwise a crash between the insert and the work will mark the delivery as seen without actually doing the work.

Pruning the table

The table grows by one row per unique delivery. A retention job that deletes rows older than ~30 days is sufficient — Awardee retries for at most 24 hours, so any id older than that will never be re-delivered. The remaining ~6 days of headroom catches manual replays from the dashboard.

What about ordering?

Idempotency dedupes redelivery. It does not order out-of-order events. For ordering, see Retries and ordering — the short version is: compare the timestamp inside the payload to whatever your downstream store has, and skip stale updates.