Recipe

Make a webhook handler idempotent

At-least-once delivery means the same event can arrive twice — after a slow acknowledgement or a retry. Deduplicate on the event id: record processed ids, and short-circuit any you have seen. This recipe shows the insert-first pattern that is race-safe.

2 min read

event.idDedup key
Insert-firstRace-safe
at-least-onceWhy it matters

The pattern

Use a unique constraint on the event id and let the database reject the duplicate for you, rather than a read-then-write that races under concurrent retries.

async function handle(evt) {
  try {
    await db.insert('processed_events', { id: evt.id });   // unique PK
  } catch (e) {
    if (isDuplicateKey(e)) return;   // already handled — no-op
    throw e;
  }
  await applyEffect(evt);            // your business logic
}

Why not read-then-write

Two concurrent retries can both read "not seen" before either writes, and both process the event. The unique-insert pattern makes the database the arbiter, so exactly one wins.

Retention

You only need to remember ids long enough to cover the 72-hour retry window. Prune older rows to keep the table small.

Frequently asked questions

What key should I dedupe on?

The event envelope id (evt_…). It is unique per event and identical across redeliveries of that event.

Does signature verification remove the need for this?

No. Verification proves authenticity, not uniqueness. An authentic event can still be delivered twice. See signature verification.

Funding for UK limited companies

Credicorp lends to your company, not to you personally — short-term working capital with no personal guarantee. See what your business could access.