Duplicate Webhooks
Understand and handle duplicate webhook deliveries.
ReturnMate outbound webhooks are delivered at least once — your endpoint may occasionally receive the same event more than once. Your handler should treat this as normal.
Why Duplicates Occur
ReturnMate retries delivery whenever it doesn't observe a 2xx response: retries fire after roughly 1s, 4s and 16s (four attempts in total), and each attempt times out after 10 seconds. This means duplicates typically come from:
- Your endpoint returned a 2xx, but too slowly — the attempt timed out and was retried even though you processed it
- A transient non-2xx (e.g. 502 from a proxy) after your handler had already done the work
- Network failures where the response was lost
Deduplicating
There is no delivery-ID header. The only headers sent are Content-Type: application/json and X-Webhook-Signature (an HMAC-SHA256 of the raw body, when you have a webhook secret configured). Retries of the same event send an identical payload, so deduplicate on the payload itself:
{
"event": "offline_repair.status_changed",
"timestamp": "2026-07-14T02:15:00.000Z",
"data": {
"id": "rma_cuid",
"rmaNumber": "RMA-2026-001234",
"externalRef": "your-reference",
"externalSource": "your-system"
}
}
Build a dedupe key from (event, data.id, timestamp) — or from your own externalRef plus event — and skip anything you've already processed:
async function handleWebhook(req) {
const { event, timestamp, data } = req.body;
const key = `${event}:${data.id}:${timestamp}`;
if (await alreadyProcessed(key)) {
return { status: 200 }; // duplicate — acknowledge and skip
}
await process(req.body);
await markProcessed(key); // unique constraint recommended
return { status: 200 };
}
Best Practices
- Return 2xx quickly — acknowledge first, do heavy work asynchronously (attempts time out after 10 seconds)
- Make handlers idempotent so a replayed event is harmless
- Enforce the dedupe key with a database unique constraint
- Verify the X-Webhook-Signature before processing