← All postsEngineering

How to stop an offline sales app from creating duplicate orders when it syncs

iotoms team · September 4, 2026 · 5 min read

A beverage distributor running nine vans out of a single depot had a route that dipped through a river valley every morning — eleven stops, maybe ninety seconds of signal at any of them. The rep didn't think twice about it. She'd sell, the app would queue the invoice, and it would sync whenever the van climbed back out of the valley. That was the whole point of an offline-first app: keep selling, worry about the network later.

Then the office started calling about a customer who swore they'd only received one delivery of soda but had two invoices on their account. Then it happened again, with a different customer, a different rep, a different van. The stock reports stopped matching what was physically loaded that morning. Someone in accounting flagged a receivables balance that was $340 higher than it should have been for a store that paid on delivery, in cash, every single week.

Where the duplicates were coming from

The pattern, once someone sat down with the sync logs, was almost boring. The app would finish a sale, write it to local storage, and immediately try to push it to the server. In the river valley, that push would sometimes get a sliver of signal — just enough to open a connection and send the request — and then lose it before the server's response made it back down. From the phone's point of view, the request had failed. No response, no confirmation, nothing. So the app did exactly what an offline-first app is supposed to do: it kept the order queued and retried it on the next sync window.

The problem is that the server had, in fact, received that order. It just never got to say so. So when the retry landed twenty minutes later with a clean connection, the server had no way of knowing this was the same sale showing up twice — it looked like two separate orders for two separate deliveries, and it processed both. Stock got decremented twice. The customer's balance got charged twice. The van's end-of-day reconciliation quietly stopped matching what was actually on the truck.

This isn't a bug in the sync logic in the sense of a typo or a bad conditional. It's a structural gap: a network can fail after the write happens but before the confirmation gets back, and no amount of "just retry harder" logic fixes that, because retrying is exactly what causes the duplicate.

Why "check if it already exists" doesn't solve it

The obvious fix looks like: before creating the order, check if one just like it already exists. In practice this falls apart fast. Two genuinely different sales to the same customer, for the same products, in the same hour, look identical to that kind of check — a rep re-visiting a store to drop an item they forgot isn't a duplicate, it's a second sale that has to go through. Timestamps don't help either, because the retry can land seconds or minutes after the original attempt, well within the window a "looks similar" check would need to catch the real duplicate — and would then wrongly reject.

The actual fix isn't about recognizing duplicates after the fact. It's about never letting the ambiguity exist in the first place.

How the fix works: the order carries its own fingerprint

Every order gets a unique ID the moment it's created on the device — not when it syncs, when it's created, while the rep is still standing in front of the customer. That ID travels with the order through every retry. When the server receives an order, it doesn't ask "have I seen a sale like this?" It asks "have I seen this exact ID?" If yes, it discards the incoming request and just re-sends the confirmation the device never got the first time. If no, it processes the order and remembers the ID.

That one design decision collapses the entire class of bug. It doesn't matter how many times the app retries, whether the retry happens in the same minute or three days later after the phone sat in a drawer with no signal, or whether the rep's phone dies mid-sync and she finishes the day on a spare device carrying the same queued orders. The ID is attached to the sale, not to the network attempt, so the server always knows whether it's looking at a new order or an old one asking to be told it already succeeded.

The same idea extends past the order itself. Each line item, each payment applied against an invoice, each stock adjustment — anything that changes a balance somewhere needs the same guarantee, because a half-synced order (items went through, payment didn't) creates its own quieter version of the same problem. The safe default is to make every write of consequence recognizable on its own, independent of how many times the network made the device try to send it.

What to check if you're evaluating a field sales app

  • Ask directly: what happens if a sale syncs successfully but the confirmation never reaches the device? Does it retry the whole order?
  • Ask what identifies an order to the server — a generated ID from the moment of sale, or something derived later (timestamp, sequence number) that a retry could recreate differently.
  • Watch a low-signal test: create a sale, kill the connection mid-sync, restore it, and check whether stock and receivables moved once or twice.
  • If reps ever share or swap devices mid-shift, confirm queued orders carry an identity that survives moving to a different phone.
  • Check whether payments and stock adjustments get the same protection as the order itself, not just the order header.

iotoms's field app generates that identity locally at the moment of sale and carries it through every retry, so a flaky connection can make a rep wait — it can't make her customer's invoice, or the van's stock count, show up twice.

See your own routes running on iotoms

Book a demo