Idempotency for shop integrations: how to build it and why it is the whole contract
Any system that retries will eventually deliver the same event twice. If the receiving side treats that as an error, or as a second order, the damage is silent and lands in your revenue reports.
Distributed systems guarantee at-least-once delivery or nothing at all. Exactly-once is not available, and anybody who claims it has built idempotency somewhere and not told you. So a shop integration has to assume every message may arrive twice, and be designed so that it does not matter.
The failure mode is not a crash. It is a quietly doubled order, which looks like a good week.
Choosing the key
The key has to identify the event, not the record, and it has to be stable across retries of the same event while differing between genuinely new ones.
- A good composite: entity type, remote id, and the record's own updated timestamp.
order:1042:2026-03-14T09:12:04Z. A retry produces the identical key; a real subsequent change produces a different one. - Not the payload hash alone — two genuine updates that revert a field produce the same hash and the second is dropped.
- Not a random id generated at send time, because a retry generates a new one and the whole mechanism does nothing.
- Unique per shop, not globally. Two shops will both have order 1042.
A duplicate answers success
When a message arrives with a key you have already processed, the correct response is a 200 with a body saying duplicate. The sender's job is done, and it stops retrying.
- Record the key before doing the work, inside the same transaction, with a unique constraint. Checking first and inserting afterwards is a race that loses under exactly the concurrency that produces duplicates.
- Let the database enforce it. An application-level check is a suggestion; a unique index is a guarantee.
- Keep keys long enough to cover the sender's retry horizon plus a wide margin. Then prune.
Upsert on a natural key, everywhere
Idempotency at the message level is half of it. The import itself should be safe to run twice regardless, which means upserting on (site_id, remote_id) rather than inserting.
That property is what makes a backfill re-runnable, a reconcile safe and a support fix — "just re-import that order" — a one-line operation rather than a data cleanup. It is worth the small extra work on every importer you write.
Order lines are replaced, not merged
This one has bitten enough shops to deserve its own rule. When an order is re-imported, its lines must be replaced wholesale, not merged into what is already there.
Merge semantics look harmless and are wrong: a line deleted in WooCommerce has to disappear here too. Under merge it persists for ever, the order total in your copy exceeds the shop's, and every revenue report is quietly high for that order. It will not be caught by a checksum on the order header, only by one that covers the lines.
The three places double-counting actually shows up
- Revenue per campaign. The most popular campaign looks best partly because it generated the most retries.
- Flow exit conditions. A duplicated order event can fire an exit twice, which is harmless, or a duplicated cart event can re-enter somebody into a flow they finished, which is not.
- Customer lifetime value and RFM. A doubled order moves somebody into a higher frequency and value band, and they get a campaign meant for a customer who does not exist.
None of these raise an alert. That is the argument for getting it right at the boundary rather than reconciling it afterwards.
Sources and further reading (4)
- Stripe — Idempotent requests
- WooCommerce — Webhooks
- PostgreSQL — INSERT … ON CONFLICT
- AWS — Making retries safe with idempotent APIs
Checked on 23 September 2026. Provider prices, mailbox rules and legal guidance change — verify anything you plan to act on.
An order that arrives twice is counted once
Auralata keys every incoming event on the shop, the record and its timestamp, answers a duplicate with a success rather than an error, and replaces order lines instead of merging them — so a retried webhook never inflates a revenue report.