Developers

Retries and duplicates

What happens when your endpoint fails, and how to handle repeats.

Retries

A delivery succeeds when your endpoint answers with any 2xx status within 10 seconds. Anything else — a 4xx/5xx, a timeout, a connection error — is retried with exponential backoff:

AttemptRoughly after
1immediately
21 minute
34 minutes
416 minutes
51 hour
64 hours
7–8~10 hours each

That is 8 attempts over about 24 hours. Redirects are not followed — point the endpoint at the final URL.

Acknowledge first, work later

Return 200 as soon as the signature checks out and do slow work (calling other APIs, sending emails) in a background job. Slow handlers hit the 10-second timeout and get retried.

Duplicates: at-least-once delivery

You may receive the same event more than once. For example: your server processes an event, but its 200 reply is lost on the network. Zaher never got the acknowledgement, so it retries.

Retrying is the safe choice — never retrying would lose events — so make your handler idempotent (running it twice has the same effect as once). The event id is identical on every retry, so record the ids you've processed and skip repeats:

export async function handleZaherEvent(event: { id: string; type: string }) {
  // A unique constraint on processed_events.id makes this race-safe.
  const isNew = await db.processedEvents.insertIfAbsent({ id: event.id });
  if (!isNew) return; // already handled

  await process(event);
}

Ordering

Events are not guaranteed to arrive in order — a retry of an older event can arrive after a newer one. Use created_at to decide which is newest, and remember data is the resource as it was when that event happened.

Failing endpoints are disabled

If an endpoint keeps failing for 3 days or 50 attempts in a row, Zaher disables it and notifies the store by email and in the dashboard. Fix the endpoint, then re-enable it from Settings → Webhooks. Re-enabling resets the failure counters.

On this page