Two inventory sync apps on one product count every sale more than once
Neither app can tell the other's write from a customer's purchase, so each one reads the other's correction as a fresh sale and passes it on again. What it looks like, how to check in ten minutes, the order to unwind it in — and why we got the diagnosis wrong on a real catalogue before we got it right.
Most stores that end up with two inventory sync apps writing the same products did not choose to, and got there for ordinary reasons. You are trialling a replacement and leave the old one running so nothing breaks during the switch. You have one app pushing stock to Etsy or Amazon and a different one keeping two Shopify stores in step, because no single app does both. An agency set one up eighteen months ago, nobody uninstalled it, and it has been quietly writing inventory ever since.
None of that is careless. But the moment two apps both have permission to write inventory on the same products, they start counting each other’s work as sales — and the failure is silent, gradual, and invisible in both apps’ logs, because both apps are working perfectly.
Why two apps collide at all
Every inventory sync app on Shopify is built the same way. It subscribes to the inventory-level webhook, Shopify fires that webhook whenever a quantity changes, and the app reacts.
The webhook carries a number, not an author. It says this inventory item at this location is now 7. It does not say whether the 7 came from a customer’s order, a staff member editing the admin, a CSV import, or another app’s API call. Shopify has no field for that, and the API a sync app reads from does not have one either.
An app can recognise its own writes, because it knows the number it just wrote and can compare. It has no way at all to recognise anybody else’s. So from app B’s point of view, app A’s perfectly correct mirror of a sale is indistinguishable from a second customer buying the same thing — and it dutifully mirrors it again.
That is the whole mechanism. It is not a bug in either app, and it does not depend on which two apps you pick. It is a structural property of two writers on one number.
It is not a race condition, and the difference matters
The explanation you will usually find for this is that Shopify applies whichever update arrives last, so two apps writing at once overwrite each other. That is a real thing — and it is not what is happening here.
Last-write-wins is about two writes landing in the same instant. It costs you one update, and the number afterwards is whatever the winner said, which is at least a number somebody meant. What happens between two sync apps is the opposite shape: the writes are seconds apart, every one of them lands cleanly, and nothing is lost at all. Every write is read — correctly, as new information — by the other app, which then makes another one.
So the error does not settle. It accumulates:
- A race condition costs you one update, once, and a re-sync fixes it.
- Two sync apps cost you one extra count per product sharing the SKU, per sale, and a re-sync makes it worse, because the re-sync is itself an inventory change that both apps can see.
If you have been told to fix this by syncing more often, or by staggering the two apps’ schedules, that advice is answering the race-condition version of the problem. Frequency is not the variable. The number of writers is.
Why a conditional write does not save you
If you have read what “real-time” actually means, you know the good apps use a conditional write — set this to 7, provided it is still 8, which this was computed from — and that this is what makes them safe under load.
It does not help here, and it is worth being precise about why. A conditional write defends against stale data: it refuses when the number moved underneath you between reading and writing. The second app’s write is not stale data. It is a real, current, freshly-committed change to that inventory level. So the first app’s conditional write is either accepted against the new value, or refused once and then re-read — and on that re-read it finds a genuine lower number and treats it as genuine.
Both apps do exactly the right thing at every step. The count is still wrong. Correctness in each app individually does not compose into correctness across two of them.
The part that turns a rounding error into a disaster
If every SKU sat on exactly one product, two apps would double-count each sale and you would notice within a day. The damage scales with something else: how many products share the SKU.
Bundles, a duplicate made for a campaign, the same item under two names, the same item in a second store — one SKU across several products is normal, and in a made-to-order or print-on-demand catalogue it is the rule rather than the exception. One blank tumbler design can carry the same SKU across hundreds of products.
Now run one customer buying one unit through two apps — app A writes first, app B is watching:
| Step | What happens | What app B sees |
|---|---|---|
| 1 | A customer buys one unit of a product | That product drops by 1. A real sale. |
| 2 | App A mirrors it across every product sharing the SKU | 284 more products each drop by 1 |
| 3 | App B reads every one of those as an independent sale | Its count for the group falls by a further 284 |
One unit sold. The group’s count falls by 285. Sell four of them over a weekend and you are more than a thousand units below zero on something you hold two of.
What this actually did, measured
In September 2026 a merchant running a print-on-demand catalogue across two stores set up shared stock with us and left it in preview mode — the state where StockUnison computes every change and writes nothing. They were also running Trunk on both stores, which is an ordinary and reasonable thing to be running; nothing below is a fault of Trunk’s, and swapping the two names around produces exactly the same outcome.
Read from our production database four days later, on 20 September 2026:
- 50 of their 3,207 groups held a count below zero.
- 21 of those were below zero and below every single product in the group — our own definition of a number that must never be written to a catalogue.
- The app had computed 541 inventory changes across those four days and written none of them, because preview mode writes nothing. That is the only reason this was caught before it did damage rather than after.
We blamed the other app, and we could not have known
Our first reading of those numbers is the one this page describes: Trunk copying each sale to the other store, StockUnison counting the copy as a further sale, multiplied across the 285 products some of this merchant’s SKUs sit on. We told him so, in writing.
Stating it as fact was wrong, and why is worth more than the original claim. In preview, nothing is written back. A group’s shared count falls by every sale, while each product falls only by its own — so the gap between them is “sales on the other products”, and on a group that nothing is wrong with it grows without limit. A second app produces that same shape. From outside, in preview, the two are not distinguishable.
We had shipped a detector that read that gap as proof. On 21 September we found it had stopped fifteen of this merchant’s groups in two days, each time telling him another app was changing his products, and it was wrong every time. It now runs only while sharing is live — where every sale is written to every product, so a product sitting above the shared count by more than one change really does mean something else is writing.
What survives is the part that matters. The number was unsafe whichever explanation was true, and preview is what stopped it reaching a catalogue.
Why your Shopify inventory keeps going down on its own
If your counts are falling with no orders behind them, the most likely cause is that something other than your customers is writing inventory — a second sync app, a marketplace or channel feed, a 3PL integration, or a scheduled CSV import — and its writes are being counted as sales on top of the real ones. You can prove or rule that out in about a minute, from a screen Shopify already gives you — step 2 below is the one that decides it. The rest of this page is what to do once you know.
None of the symptoms look like “two apps are fighting”, though. That is the problem.
| Symptom | What it gets blamed on |
|---|---|
| Counts drifting downward with no orders behind them | Reads as shrinkage, theft, or a bad stocktake |
| A number that ratchets down every few minutes | Reads as a busy sales day, until you check the orders |
| Quantities going negative on items you definitely hold | Reads as a bug in whichever app you happen to be looking at |
| One app’s log saying “synced”, the other disagreeing about the number | Both logs are honest — both writes succeeded |
| Products going out of stock and hiding themselves | Reads as a merchandising or theme problem |
| Counts climbing after a restock, not falling | Reads as a miscount at goods-in |
| A bulk import “not taking” | The other app overwrites it within seconds |
Two of those deserve more than a row.
It goes up as well as down. The direction follows whatever the change was. Sales are the common case because sales are constant, so downward is what people notice — but a restock, a return, or a stocktake correction gets multiplied by exactly the same arithmetic and sends the count the other way. If your counts are rising on their own, this page is still the page.
Fixing it by hand while the cause is live makes it worse. If you correct the numbers by CSV while both apps are running, the correction is itself an inventory change, both apps see it, and both mirror it. This is why the order in the next two sections matters more than any single step in them.
How to check, in about ten minutes
1. List what has permission to write inventory. Shopify’s admin lists every installed app under Settings → Apps and sales channels. Go through it and ask of each one: could this write a quantity? Sync apps, marketplace and channel connectors, 3PL and warehouse integrations, bundle apps, POS extensions, forecasting apps that also “push” reorders, and any private or custom app an agency built for you. Anything installed and forgotten counts — an app does not have to be open in a tab to be writing.
2. Read one product’s adjustment history. This is the decisive test and it takes a minute. Open a product in the admin, go to the variant’s inventory, and view its inventory adjustment history. Shopify records the author of each adjustment, app names included. What you are looking for:
- two different app names alternating down the list on the same item
- one app name appearing far more often than you have orders
- adjustments with no order attached, arriving in bursts
3. Pick a SKU that sits on several products and count. Add up what the products in that group say they hold, against what is on the shelf. A gap much larger than your genuine shrinkage, on an item with no returns problem, is this.
4. Look for negatives. A negative count can be an honest oversell. A count that is negative and lower than every single product in its group is a number that must never reach a catalogue — but read it carefully, because what it proves depends on where you are. Once sharing is live, every sale is written to every product in the group, so that gap means a change was counted more than once. In preview nothing is written back, so the shared count falls by every sale while each product falls only by its own, and ordinary trading opens the same gap. There it tells you the number is unsafe, not that something else caused it.
The safe way out
The order matters more than any single step, and the commonest mistake is doing step 3 before step 1.
- Decide which app owns inventory. One app. Not “one per store”, not “one for marketplaces and one for stores if their products overlap” — one writer per inventory item. If two tools genuinely have to write, they have to write disjoint sets of SKUs, and you need to be able to name which is which.
- Turn the other one off properly. Stop its syncing inside the app, not by uninstalling from the admin — uninstalling revokes access before the app can tidy up, and what an app does on the way out varies wildly. Confirm it is off rather than assuming; a paused connection and a disabled one are not always the same thing.
- Wait for quiet. Give it a few minutes with no orders landing. Anything queued in either app should finish writing before you measure anything.
- Re-baseline from what the products actually hold now. Not from what either app believes, and not from a backup. Count the physical stock, set those numbers in the store that holds the goods, and treat that as the new zero. Any correction you make while a second writer is live will simply be mirrored and re-counted.
- Let the remaining app read fresh. In StockUnison that is Settings → “Start the numbers over”, which rebuilds every count from what your products hold right now and drops sharing back to preview mode, so nothing is written until you say so. Whatever app you keep, the equivalent step exists and you want it — an app carrying counts from the fighting period forward will carry the errors forward too.
- Read the preview before you go live. This is the cheap check that would have caught the case above on day one. If any group still shows a number that cannot be right, the cause is still running somewhere.
- Test with an order that has two line items sharing one SKU. Place a real order containing two different products that carry the same SKU, and watch what each product ends up holding. This is the exact case that breaks naive implementations, and it will not show up in a single-item test.
When two apps are genuinely fine
This page is not an argument for one app. Plenty of stores run several without trouble, and the distinction is simple:
- They write different products. No SKU overlap, no shared inventory item — no collision. Worth confirming rather than assuming, because the same SKU turns up in more places than people expect.
- One of them only reads. Forecasting, reporting, reorder planning and analytics apps generally take no write permission at all. They cannot cause this.
- They write different locations. Shopify tracks a quantity per location, so two apps owning two locations of the same item are not writing the same number. This is narrower than it sounds — check that a sale actually draws from the location you think.
The one combination people believe is safe and is not: a marketplace or channel feed plus a store-to-store sync, both acting on the same Shopify products. Two jobs, two apps, one number. That collides exactly like any other pair.
What StockUnison does about it
Three things, and one of them is a limit rather than a feature.
It refuses to go live on numbers that cannot be right. Since 19 September 2026, if any group holds a count that is below zero and below every product in it, going live is refused and says why — on every route to live, not just the obvious button. The narrow definition is deliberate: a genuine oversell produces an honest negative, and that must still be allowed through.
It watches for a second writer and stops rather than guessing. When a group’s counts move in the pattern a second app leaves behind, that group is paused for review and carries a note in plain words: “Another app or an import is changing these products too, so each sale is being counted more than once. Turn it off, then start the numbers over.” The engine stops propagating that group instead of writing a number it cannot explain.
And the honest limit: it cannot tell you which app, and it cannot fix it for you. The signature of a second sync app and the signature of a scheduled CSV import are the same signature. We can tell you a group’s arithmetic stopped adding up and refuse to act on it; you have to find the other writer and turn it off. Detection landed in September 2026 — it is new, and it fires on a pattern rather than on certainty.
The thing that actually saved the merchant above was none of the three. It was preview mode: StockUnison starts every connection computing and writing nothing, and stays there until you press go live. Four days that left fifty of their groups below zero cost them nothing at all, because not one of those numbers ever reached a product. If you are evaluating sync apps while already running one — which, on our evidence, is what most people are doing — that is the property to insist on, from us or from anyone. The free plan includes it, and choosing a sync app has the rest of the questions worth asking first.
Common questions
Can you run two inventory sync apps on the same Shopify store?
Only if they touch different products. If both apps have permission to write inventory and both act on the same SKUs, they will fight, because neither can tell the other's write from a customer's purchase. Shopify sends the same inventory webhook for both, and the payload carries a number, not an author. Each app therefore reads the other's correction as a real sale and passes it on again.
Why does my Shopify inventory keep going down on its own?
The usual cause is two things writing the same inventory item — a second sync app, a marketplace feed, or a scheduled CSV import. Each sale gets counted once by the real order and once more by every other writer that mirrors it, so the count ratchets downward with nothing in the order history to explain it. Shopify's inventory adjustment history names the app behind each change, which settles it in about a minute.
Why is my Shopify inventory going up on its own?
The same cause, running the other way. Two writers multiply whatever the change was, so sales drive counts down and a restock, a return or a stocktake correction drives them up. Sales are constant, which is why downward is the version people notice and write about, but a goods-in of 40 mirrored across the products sharing that SKU lands just as wrongly.
Does a conditional or compare-and-set write stop two apps fighting?
No, and this is the part that surprises people. A conditional write protects you from a stale read: it refuses if the number moved since you read it. The second app's write is not stale data — it is a genuine, current change to the inventory level. The first app re-reads, sees a real new number, and acts on it correctly. Both apps behave impeccably and the count is still wrong.
How do I tell which app changed a quantity?
Open the product in your Shopify admin, go to the variant's inventory, and view its adjustment history. Shopify records who made each adjustment, including the app name. If two app names alternate down the list on the same item, or one app name appears far more often than you have orders, you have found it.
What is the safe order to switch from one inventory sync app to another?
Turn the old one off first, then fix the numbers, then start the new one. Never run both live on the same products, even briefly, and never re-baseline a new app while the old one is still writing — you will simply record the wrong number as the new truth. Stop the old app's syncing inside the app, give it a few quiet minutes to finish anything in flight, count what you actually hold, set that in the store, and only then let the new app read and go live.
One item. One number.
StockUnison keeps one count wherever an item sells — across your Shopify stores, or across duplicate products in one. Matched by SKU, previewed before anything moves, and logged write by write.
Install on ShopifyFree plan you can stay on · live syncing from $19/mo