Duplicate orders are the most common symptom of an unreliable integration: the same marketplace order lands in the store twice, the warehouse reserves double stock, and the customer gets two parcels or one invoice too many. The problem is not operational — it is a design problem. Duplicates must be eliminated at the source, at the idempotency key level, not filtered after the fact.
KEY: A duplicate appears when a retried request after an API timeout creates a second record, because the system cannot recognise it as the same order. The fix is idempotency: every order carries a unique marketplace identifier, and before saving, the system checks whether that identifier already exists — if so, it updates the record instead of creating a new one.
Where duplicate orders come from
Three mechanisms cause the vast majority of duplicates
Retry after timeout. The integration sends a request, the marketplace does not answer within the time limit, the system retries. The “failed” request actually succeeded — and now the order exists twice. This is the most common cause.
Double webhook subscription. The marketplace sends order notifications to two endpoints (old and new), or a webhook is delivered more than once (which is normal — webhooks are “at-least-once”). Without deduplication, every delivery creates an order.
Cross-import. Sync runs both ways, and an order created in the store “returns” from the marketplace as new, because there is no shared identifier on both sides.
- The common denominator: the system cannot answer the question “do I already have this?”. Idempotency is exactly that ability.
What idempotency means in practice
Idempotency is the property of an operation that can be executed many times with the same effect as once. In e-commerce integrations it means one thing: a retried order-creation request must not create a second order.
The implementation is simple in concept and demanding in detail
- Every marketplace order carries a unique identifier issued by the marketplace.
- Store that identifier alongside the order in your system (as a dedicated field or part of a key).
- Before creating an order, check whether an order with that identifier already exists.
- If it exists — update it (status, data) and stop. If not — create it.
The check must be atomic: two parallel processes must not both “check and miss”. In the database, a unique index on the marketplace order identifier solves this — the second insert simply fails and lands in duplicate handling instead of creating a new order.
The implementation pattern
The pattern that proved itself in our deliveries (Laravel + PostgreSQL)
Idempotency key. An external_order_id column with a unique index, unique within a channel (channel + external_order_id). The same order number on Allegro and Amazon is two different things — the channel is part of the key.
Operation order. Attempt the insert with the key first, then everything else (stock reservation, invoice, notifications). If the insert fails due to a duplicate — processing ends with an update, without re-running side effects.
Side effects must be idempotent too. Deduplicating the order is not enough if stock reservation, emails or invoices run on every webhook delivery. Every step must be safe to repeat: reservation checks whether it exists; the invoice checks whether it was already issued.
Event log. Every webhook delivery and every write attempt lands in a log with the outcome (created, updated, duplicate_ignored). Without it, you cannot answer “why does this order exist twice” — and that question returns with every incident.
Retry queues — how to retry without duplicating
A retry queue is essential — without it, every transient API error means a lost order. But a queue without idempotency is a duplicate factory. The rules that work together:
- Retry with exponential backoff (e.g. 1 s, 5 s, 30 s, 5 min) instead of instant repeats — gives the API time to recover and reduces partial successes.
- Idempotency on the receiving side — no matter how many times a job returns from the queue, the end state is one.
- Rate limiting — the queue respects API limits instead of hammering them with parallel requests and provoking the timeouts that generate retries.
- Dead letter queue — jobs that failed N times go to a separate queue for manual review, instead of disappearing or looping forever.
Webhooks: “at-least-once” means “expect duplicates”
- Worth saying plainly: webhooks are delivered at least once, not exactly once. Every serious provider (marketplace, payment provider, carrier) can deliver the same event twice — on sender-side retries, network issues, or when deploying a new endpoint version. This is not a provider bug; it is the nature of the protocol.
The design consequence: your webhook endpoint must be idempotent by default. The minimal set of practices
- Signature verification (HMAC) — rejects requests that are not from the provider.
- Event key (event ID) — deduplicates deliveries of the same event.
- Fast 2xx response and asynchronous processing in a queue — the marketplace does not wait for the whole flow.
- Delivery log — the ability to reconstruct what arrived and when.
Finding duplicates that already exist
If duplicates have been accumulating for months, find them before rolling out idempotency. Two techniques
Duplicate report. A query grouping orders by external identifier and channel — groups with more than one record are duplicates. It usually reveals a pattern: duplicates in a specific time window (API outage) or on a specific channel (broken webhook subscription).
Side-effect audit. Order duplicates often drag along duplicate invoices, reservations and payments. Before fixing the flow, inventory the damage: which invoices were issued twice, which stock was over-reserved.
Repairing historical data is a separate stage — idempotency stops new duplicates, it does not remove the old ones.
What fixing the flow costs
Rebuilding the flow with idempotency, a retry queue and an event log usually takes 2–4 weeks, depending on channel count and the state of the current integration. Cost: 4,000–15,000 PLN net for a typical multi-channel store. If duplicates have existed for a long time, add the audit and historical data repair.
For comparison — the cost of one duplicate: a double shipment, a return, support time, invoice and reservation corrections. With dozens of duplicates a month, you are paying for a full-time position.
Case study: zero duplicates after rebuilding the flow
In our omnichannel sales integration case study we describe a store where duplicates occurred regularly — the team checked every order by hand in two systems. After introducing an idempotency key based on the marketplace identifier and a pre-save check, duplicates dropped to zero — not by filtering, but by making them impossible. Manual order verification disappeared and order handling time fell by 40%.
If your integration duplicates orders, see the e-commerce and API integrations service — we start with an audit, not a rewrite.
Frequently asked questions
How do we avoid duplicate orders in marketplace integrations?
- The key is idempotency: every marketplace order carries a unique identifier stored with the order (including the channel). Before creating an order, the system checks whether that identifier already exists — if so, it updates the record instead of creating a new one. A unique database index protects the check so two parallel processes cannot create two orders.
Why do webhooks create duplicate orders?
Webhooks are delivered “at least once” — the provider may send the same event twice on network issues or its own retries. That is normal protocol behaviour, not a bug. Your webhook endpoint must therefore be idempotent: verify signatures, deduplicate events by identifier and process them asynchronously in a queue.
Is filtering duplicates after the fact enough?
No. Filtering treats the symptom, not the cause — duplicates still appear and still generate side effects: double stock reservations, invoices, customer emails. Duplicates must be eliminated at the source, at the idempotency key level, and every side effect in the flow must be made idempotent as well.
How do we find duplicates that are already in the system?
A query grouping orders by external identifier and channel — groups with more than one record are duplicates. Also inventory the side effects: double invoices, reservations and payments. Repairing historical data is a separate stage after rolling out idempotency.
How long does fixing a duplicate-producing integration take?
Rebuilding the flow with idempotency, a retry queue and an event log usually takes 2–4 weeks, depending on channels and the state of the current integration. If the system has been collecting duplicates for months, add time for the audit and historical repair.
Is idempotency enough to fix drifting stock levels?
Idempotency fixes duplicate orders, but stock drift has additional causes: missing safety buffers, delayed synchronisation and manual edits outside the integration. The full set is order idempotency, a single source of truth for stock, per-product buffers and reconciliation with alerts.
Sources and further reading
- Case study: omnichannel sales integration — zero duplicates after idempotency
- Resilient API and webhook integrations — retries, HMAC signatures, queues
- Stock synchronisation across sales channels — buffers and reconciliation
- Omnichannel integration — what it is and how to implement it — the model in depth
- Marketplace integration — channel integration models