A customer is charged twice for one order. Two parcels arrive at a customer’s door, and the store pays for both. A supplier receives the same purchase order on Monday and again on Tuesday. A welcome email lands three times in a minute. Each of these stories has a villain in the retelling, a glitchy app or a careless contractor, but the cause is usually the same plain fact: something happened twice, and nothing in the system was designed to notice.
This guide explains why repeats happen and the technique that prevents their damage, called idempotency. It is a technical idea, but it is simple to grasp and it is the single most valuable habit in building automations that move money, stock and messages. We use store examples and avoid code. The concept ties together our guides on Shopify webhooks, API rate limits and order routing, because all of them depend on it.
What idempotent means
An action is idempotent if performing it several times has the same effect as performing it once. Setting a thermostat to twenty degrees is idempotent: do it ten times and the room is still at twenty. Adding one degree is not: do it ten times and the room is ten degrees hotter. Many store actions are naturally the second kind, such as sending an email, creating a supplier order, issuing a refund or adding one to a stock count. They only behave safely when repeated if you make them idempotent on purpose.
| Action | Naturally idempotent? | Why | Safe way to repeat |
|---|---|---|---|
| Set a tag on an order | Yes | The tag is either there or not | Nothing special |
| Set stock to a number | Yes, but risky | Same result, but may overwrite other changes | Prefer adjustments with keys |
| Add one to stock | No | Each repeat adds again | Use a key so the add happens once |
| Send an email | No | Each repeat sends another | Record that it was sent for this order and message |
| Create a supplier order | No | Each repeat creates another | Key on the store order and line |
| Issue a refund | No | Each repeat pays again | Key and check refunded amount first |
| Charge a card | No | Each repeat charges again | Use the payment provider’s idempotency keys |
Why repeats happen
- Retries after timeouts. A webhook receiver is slow, the sender assumes failure and sends again, although the first one eventually succeeded.
- At-least-once delivery. Many systems promise that an event will be delivered at least once, which means it may be delivered more than once. Shopify’s documentation, for example, says duplicates are minimised but can still occur.
- Double clicks and refreshes. A customer taps the pay button twice, or a team member presses run twice.
- Overlapping schedules. A job that runs every five minutes starts again while the previous run is still going.
- Recovery and replays. After an outage, you replay the missed events, some of which had in fact been processed.
- Workflow tools that retry failed steps. The step that failed may have partly succeeded before it failed.
- Two paths to the same effect. A flow and an app both react to the same order.
Because repeats cannot be eliminated, the sensible response is not to try to make the world deliver everything exactly once, which no practical system can guarantee, but to make the receiving side safe. Delivery that may repeat, combined with actions that tolerate repeats, behaves as if everything happened once.
The idempotency key pattern
The standard technique has four steps. Build a key that uniquely identifies the intended action. Check whether that key has been used. If not, do the action. Record the key and, ideally, the result. A repeat of the same request arrives with the same key, finds it recorded and does nothing, or returns the original result.
| Step | What happens | Example |
|---|---|---|
| 1. Build the key | Combine stable facts that identify the action | supplier-order:shopify-order-10482:line-2 |
| 2. Check | Look the key up in a record of completed actions | Has this key been recorded? |
| 3. Act | Perform the action only if the key is new | Create the supplier order |
| 4. Record | Store the key and the result | Save the supplier’s order number against the key |
| 5. Repeat safely | A repeat finds the key and returns the saved result | The second event creates nothing |
Choosing a good key
- Stable. The same logical action must always produce the same key. A timestamp or a random number would defeat the purpose, unless the client generates it once and reuses it on retries.
- Specific. Include the order, the line and the action, so that different actions on one order do not collide.
- Free of sensitive data. Payment providers advise against using personal information such as email addresses in keys.
- Long enough to avoid collisions. Random identifiers generated once per request, such as UUIDs, are a standard choice when the client creates the key.
Payment providers build the pattern into their APIs. Stripe’s documentation, for instance, describes an idempotency key header on create and update requests: the provider saves the result of the first request made with a key and returns the same result to retries, and it compares the parameters of a retry with the original and errors if they differ. It notes that keys can be removed automatically after at least twenty-four hours, so a key reused much later is treated as a new request. Check your provider’s current documentation for the details; the lesson is that the best defence uses both your own record and the provider’s support.
The race condition trap
There is a subtle way to get this wrong. If your system first checks whether the key exists and then, in a separate step, creates the order, two runs arriving at the same instant can both pass the check before either has recorded the key. Both then act, and you have a duplicate despite your careful design. The fix is to make the check and the record a single, atomic step: attempt to insert the key, and treat a failure because it already exists as the signal to stop. Databases support this with unique constraints, and some workflow tools offer a built-in way to claim a key.
| Design | What can happen | Verdict |
|---|---|---|
| Check, then act, then record | Two runs both pass the check and both act | Unsafe under simultaneous events |
| Record first, then act | If the action fails after recording, it never happens | Safe from duplicates, needs a retry path for failures |
| Atomic claim of the key, then act, then mark done | Second run cannot claim the key; an unfinished claim can be retried after a timeout | Recommended |
Idempotency in no-code workflows
Workflow tools such as Zapier, Make and n8n rarely have idempotency built in, but you can add the pattern. Keep a small table, in a spreadsheet, a database or the tool’s own data store, with one row per key. At the start of the workflow, search for the key; if found, stop. If not found, write the key, then proceed. For the race condition, rely on a store that supports atomic writes where simultaneous runs are likely. Our comparison of Zapier, Make and n8n notes how each tool handles retries and duplicates.
- Add a Do I already have this? step right after the trigger.
- Use the event’s own ID when the platform provides one, such as a webhook ID.
- Write the key before the risky action, and mark it complete after.
- Alert on keys that stay unfinished beyond a timeout.
- Clean old keys after a retention period, longer than any retry window.
Idempotent design beyond keys
- Prefer set to add. Where possible, record the desired state rather than a change, so that repeats converge on the same state.
- Use state machines. An order moves from created to routed to confirmed to shipped; an event that does not fit the current state is ignored.
- Reconcile. A scheduled check compares what should exist with what does, and fixes both missing and duplicate items.
- Make side effects visible. Log every action with its key so that a duplicate can be found and explained.
- Guard money with limits. A cap on refunds per order stops a repeat from paying out twice.
How to test for it
Automations are usually tested by running them once and watching them work. Idempotency problems appear only when things go wrong, so test the wrong paths on purpose.
- Replay the same event three times and check that only one action results.
- Send two identical events at the same moment to test the race condition.
- Interrupt a run halfway, for example by failing the second step, then retry and check for a half-done or doubled result.
- Click the button twice on any customer-facing action.
- Change a parameter on retry and check that the system rejects or handles the mismatch sensibly.
- Replay a day of events after a simulated outage and compare the result with the original.
A worked example
A store routes orders to a supplier through a workflow triggered by an order webhook. During a flash sale the supplier’s API is slow, so the workflow takes eight seconds to respond, and the platform records a timeout and resends 40 of the events. Without idempotency, 40 orders produce 80 supplier orders. At an average cost of $22 per order, that is $880 of unwanted stock, plus the cost of returning or absorbing it and the time to find out. With a key built from the store order and line, checked and claimed atomically, the second events find the key and do nothing. The extra cost of the design is perhaps an hour of build time, which is why it belongs in every workflow that creates things. It is built into our order routing to suppliers and ecommerce workflow automation services, and our Shopify order automation service applies it to tags, holds and notifications.
Sources and further reading
Figures and rules in this guide come from the primary pages below. Platform limits and prices change, so open the page before you build on a number.
- Stripe documentation: idempotent requests, how keys are saved, compared and expire, read 20 October 2026
- Shopify developer docs: HTTPS webhook subscriptions, duplicate deliveries and the webhook ID header
- Wikipedia: idempotence, the general concept