Why Register Inventory and Online Inventory Keep Going Out of Sync — What to Check, in Order
The register says twelve. The website says nine. Somebody has to decide which number is real before the next customer buys the last one.
What follows is an order to work in, not a ranking of what happens most often. Nobody has trustworthy public data on the relative frequency of these causes, and anyone who gives you a percentage is guessing. The order below is chosen on a different basis: cheapest checks first, and the ones that would make the rest of the investigation pointless first of all.
Two warnings before the list. Count the shelf before you open anything, because until you know the true figure you cannot tell which system is wrong, or whether both are. And be ready for the answer to have nothing to do with syncing.
First: are these even the same number, for the same place?
Two questions kill a large share of investigations before they start.
Is the channel selling from that location at all? Stock is held per location, and a sales channel is usually scoped to some locations and not others. If the website fulfils from the warehouse and that store is not enabled for online orders, the register and the product page are describing different places, and they are both right.
Is each screen showing the same kind of number? Inventory is not one figure per item per location; it is several. Shopify publishes its model plainly, and it is a useful worked example even if your platform names things differently: on hand equals committed plus unavailable plus available. On hand is "All inventory units that you have at a location". Available is "Inventory that you can sell" which "isn't committed or set aside as Unavailable".
The middle two states are where the confusion lives. Committed covers "Units that are set aside and can't be sold, such as units in an unfulfilled order, reserved in a draft order, or in a transfer marked as ready to ship." Unavailable covers "Units set aside by apps or held for other reasons, such as damaged, quality control, or safety stock." Incoming stock, "on its way to your location from transfers, purchase orders, or apps", is not sellable "until it's been received" even once the pallet is physically in the building.
So twelve on a back-office screen and nine on a product page can both be correct, with three units sitting in orders that have not shipped. Check your own platform's terms before assuming a fault: most have equivalents, but the naming and the defaults differ, and some registers show available rather than on hand.
Then: read the audit trail against your count
Inventory adjustment history records "who made the change, when it happened, and how it affected your quantities", with a reason attached: correction, count, received, return restock, damaged, theft or loss, promotion or donation, plus automatic entries such as transfer created, removed from location, and reservation created, updated or deleted. Each row shows the effect across available, committed, unavailable, on hand and incoming.
Read it against your physical count, not on its own. A list of entries will not show you the movement nobody recorded; the gap only appears when the ledger is compared with the shelf or with sales records. Note also that the history names apps as well as people, which is how you find out that something other than a human has been writing to the number.
One practical limit: on Shopify the product view holds only the last 180 days, so older drift needs the separate adjustment report, filterable by SKU, location, staff member, app and reason.
Cause: two systems writing the same number
This is the one worth understanding properly, because the usual request to a vendor makes it worse.
When a register and a website disagree, the request is often to sync more often. If both systems write absolute quantities rather than changes, a shorter interval mostly buys you more collisions.
Shopify's developer documentation for inventory apps is direct about the mechanism. There are two ways to write a quantity: set it to an absolute value, or adjust it by a relative amount. Both support a compare-and-swap field that checks the quantity has not moved since your system last read it. If it has, the write is rejected: "If the current quantity doesn't match the value that you provide, then the mutation fails with a CHANGE_FROM_QUANTITY_STALE error, preventing unintended overwrites."
Without that check, two writers overwrite each other. The documentation works the example: "The final available inventory value is 90, but it should be 70 (20 units lost)." Note the direction, because it decides what your customers experience. The system ends up believing it has twenty units more than it does. That is the phantom-stock version, and it shows up as oversells and cancelled orders rather than as lost sales.
The design rule underneath: decide which system owns the number, have the others send changes rather than totals, and reject writes based on a figure that has already moved. If you move to sending changes, make them idempotent, because a delta replayed after a timeout applies twice and creates the drift you were trying to remove.
The signature of this fault is not a missing history entry — an app write is recorded and attributed. It is an adjustment attributed to an integration that does not correspond to any sale, receipt or count you can find.
Cause: the register was offline
Shopify states it plainly: "Shopify POS can't sync orders and inventory with your Shopify admin when you're offline", and "When you reconnect to the internet after being offline, your orders and inventory should sync automatically." Sales made in that window are real, but nothing else knows about them yet, so the website can keep selling units that left the store an hour ago.
There is a sharper edge in the same documentation: staff should not sign out or power the device down while offline, because "Logging out of Shopify POS, or turning off the device might cause loss of offline orders." Those sales are usually still reconstructable from card settlement records and customer receipts, but that is manual work nobody has time for. The risk window is any sign-out, reboot or handover, so the check belongs at shift change rather than only at close.
Cause: returns, in both directions
The obvious failure is a refund where the unit never goes back into sellable stock: the customer is made whole at a busy counter, the item goes behind the desk, and no restock is recorded.
The more expensive failure runs the other way. A return that restocks automatically puts a worn, opened or damaged unit straight back on the website, which sells it and then cancels. If your refund flow restocks by default, someone has to be able to say no at the counter.
Cause: stock between places
A transfer marked as ready to ship commits units at the origin before they leave and lands them as incoming at the destination, not sellable until received. Systems differ here: some decrement at dispatch, some at receipt, some hold a separate in-transit bucket, so check which yours does before drawing conclusions.
Whatever the mechanism, in-transit stock needs an owner and an expected age tied to your actual circuit. A weekly van run is not late at 48 hours; a same-day courier is. Set the threshold to the route, or the alerts become noise.
Cause: one item, two records
The same physical product exists twice, under two barcodes or two SKUs, often because someone created a record at the counter that the catalogue already had. Both records are then wrong against the shelf, because each sees only part of the traffic.
Do not expect to find these by filtering on a SKU, since you do not yet know the second one. Look for duplicate or near-duplicate barcodes and titles, variants set up twice, and items where a physical count matches neither record.
The causes that are not sync at all
Several common explanations never involve an integration, and an ops team that skips them can spend a week on architecture for a shrink problem.
Units leave without being sold: theft, unrecorded damage, breakage, samples. That is why theft or loss and damaged exist as adjustment reasons in the first place. Mis-scans do the same quietly, and worse, because selling the size 10 when the size 9 left the shelf creates two offsetting errors that no history will flag as odd. Receiving errors put the wrong quantity in at the start. And a "continue selling when out of stock" setting left on will happily sell a number that no system ever claimed to have.
Marketplaces add their own layer: they hold reserved quantities, run their own refresh lag and apply their own buffers, so a marketplace figure disagreeing with your own is often working as designed.
What to check, in order
- Count the shelf, so you know which number is wrong.
- Confirm the channel is selling from that location.
- Confirm both screens show the same kind of number: on hand against available.
- Read the adjustment history against your count and your sales records.
- Check whether more than one system writes that number, and whether any writes totals instead of changes.
- Check whether a register was offline, and whether a device was signed out or rebooted during it.
- Look for unrecorded restocks, and for automatic restocks that should not have happened.
- Check transfers in flight and anything in receiving.
- Look for a duplicate item record.
- Then treat it as shrink, mis-scan or receiving error, and count that item more often.
What actually fixes it
One number should have one owner, with everything else sending changes to it rather than broadcasting totals. Square's documentation shows what that looks like when it works: stock held per location, with register sales, invoices and orders shipped through the online store all drawing down the same tracked count. The trouble starts when two systems each keep their own version of that count and both write it.
So the durable question is not how fast you sync but which system holds the one true position and what the others may do to it. That is the argument for a central system above the register rather than wiring stores to channels one pair at a time.
It will not end discrepancies. Shrink, mis-scans and miscounts carry on at whatever rate your operation produces them, which is exactly why cycle counting exists and why the retail inventory management routine matters as much as the integration. What a single owning system does remove is the class of drift nobody can explain: the units that were never sold, never stolen and never counted, and simply got overwritten.
Frequently asked questions
Will syncing more often fix stock going out of sync?
Not on its own, and it is the wrong first move. Where two systems each write an absolute quantity rather than a change, a shorter interval gives you more collisions, not fewer. Shopify's developer documentation works an example where a lost update leaves the system showing twenty units more than it actually has, which is the version that causes oversells. Its recommendation is a compare-and-swap check.
Why does my store show more stock than the website lets people buy?
Often because the two screens show different numbers. On Shopify, for example, a location's on-hand figure includes units that are committed to unfulfilled orders or held as unavailable, while the channel can only sell what is available. The other frequent explanation is location scope: the website may not be selling from that store at all, so you are comparing two different places.
How do I find out what changed a stock number?
Count the item first so you know which figure is wrong, then read the inventory adjustment history. It records who or what made each change, when, and how it affected each quantity, with a reason such as correction, count, received, return restock, damaged, or theft or loss. On Shopify the product view holds 180 days, and a separate report covers older data filtered by location, staff or app.
Start your 14-day free trial
See how OmniOrders connects your sales channels, 3PLs, and carriers into one operational layer — free for 14 days, no credit card.