docs
Get startedTroubleshootingPaymentsWebhooksAgent paymentsSwop MCPOviIdentity (swop.id)SwivyRPC (preview)API reference

Retries and de-duplication

What Swop does when your endpoint is down, and how to handle the same event twice.

Timing

Swop checks for newly paid and newly released orders about once a minute, so expect up to a minute or two between the sale and the webhook. A missed check only adds delay; it never loses an event.

Retries

A delivery succeeds when your endpoint returns any 2xx within 10 seconds. Anything else (a 4xx, a 5xx, a timeout, a redirect) is retried:

AttemptWhen
1Immediately
21 minute later
35 minutes after that
430 minutes after that
52 hours after that
612 hours after that

After the sixth failed attempt (about 15 hours in all) the delivery is marked failed and not tried again.

Return 2xx fast

Do the minimum in the request: verify, store the event, return 200. Do slow work (emails, label printing, ERP sync) in a background job. A slow endpoint looks like a failure after 10 seconds and gets the event again.

Pausing

If 25 deliveries in a row fail, Swop pauses the webhook and stops sending to it. The Developer page shows it as paused. To resume, fix your endpoint, then remove the webhook and add it again (this issues a new signing secret).

De-duplication

You may receive the same event more than once, for example if your server did the work but timed out before answering. Make your handler idempotent:

  • Each delivery has a Swop-Delivery-Id header that stays the same across retries of that delivery. Store the IDs you've processed and skip repeats.
  • Swop queues at most one delivery per webhook, event and order, so event + data.orderId is also a safe key.
// Example with any key-value store that supports "set if not exists".
const key = `${event.event}:${event.data.orderId}`;
const firstTime = await store.setIfAbsent(key, Date.now());
if (!firstTime) return res.sendStatus(200); // already handled

Order of events

Don't rely on arrival order. For a sale paid out straight away (digital or in person), checkout.paid and payout.released can be queued in the same pass and arrive close together in either order, and a retried delivery can land after a newer one. Treat each event as "this milestone has happened" and, if you need the current state, read it with GET /orders.

On this page