Stores, Amazon and Wholesale Want the Same Unit: Keeping One Inventory Position
You have eleven of something. Six are in the distribution centre, four are spread across three stores and one has been sitting in a fourth store since spring. The website can sell some of them, the marketplace listing can sell some of them, a shopper standing in store nine can pick one off the shelf, and a wholesale customer has an accepted order for six in November.
Eleven is the only honest number, and almost nothing in your stack shows it.
Overselling out of that situation has several causes, and they are worth separating because they have different fixes. Feed and API lag, throttled calls, a webhook backlog, a register running offline, and plain wrong store counts all produce oversells, and most of them do get better with faster or more reliable syncing. One cause does not, and it is the one this post is about.
The oversell that a faster sync cannot fix
Picture the sync working perfectly. Two shoppers, one in a store and one on a marketplace, act on the last unit inside the same instant. Both channels read the number correctly. Both were right. There is still one unit.
The gap there is not between updates, it is between reading a number and acting on it. Shortening the sync interval narrows that window and never closes it. It is a small share of your oversells in absolute terms, but it is the residue that survives every integration project, which is why teams who have already spent heavily on sync speed still see it on their fastest lines.
What closes that particular window is a hold: one system sets a unit aside and every channel respects it. Each of the three platforms below implements holds for its own sales. None of them lets an outside system place one.
You probably cannot put the hold where you want it
This is the constraint that most advice on this topic skips, and it is written plainly in the platforms' own documentation.
Shopify's developer documentation states that "you can't use the GraphQL Admin API to adjust or move inventory quantities in the committed state" and that quantities in that state "are managed by Shopify through actions such as creating and fulfilling orders". Your order system cannot reach in and commit a unit. On the marketplace side, the quantities in Amazon's fulfilment model describe stock Amazon is physically holding and managing, not a field a seller sets. And no software holds a unit against a customer in store nine who has already picked it up and is walking to the till.
So the realistic version of "one source of truth" is narrower than the phrase suggests. You are not going to place a lock that spans a register, a storefront and a marketplace. What you can do is own the calculation in one system, decide there what each channel is allowed to sell, and publish that number outward. The hold lives in your available-to-sell figure, not in the channels.
That is a smaller claim than the usual one, and it is the one that survives contact with the documentation.
Available does not mean the same thing on any two systems
A Shopify location breaks stock into states a merchant can see. 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". Committed is "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 that's marked as ready to ship" — note that it is wider than unfulfilled orders. Unavailable is "units set aside by apps or held for other reasons, such as damaged, quality control, or safety stock".
The API decomposes that differently. The developer documentation says the on hand state "equals the sum of inventory quantities in the following states: available, committed, reserved, damaged, safety_stock, quality_control". There is no unavailable in that list, because the API splits what the merchant screen groups together. Worth knowing before you go looking: reserved is an API state, not something your staff will find in the admin.
Amazon's model, for stock held in its fulfilment network, cuts the pie differently again. Fulfillable quantity is "the item quantity that can be picked, packed, and shipped". Total reserved quantity is "the total number of units in Amazon's fulfillment network that are currently being picked, packed, and shipped; or are sidelined for measurement, sampling, or other internal processes", and it breaks into units "reserved for customer orders", units "being transferred from one fulfillment center to another", and units "sidelined at the fulfillment center for additional processing". Two of those three have nothing to do with a customer buying anything: they are Amazon moving stock around its own network and pulling units aside for its own processing. If you are merchant-fulfilled rather than holding stock with Amazon, none of these fields describes your position at all.
eBay takes a third approach, letting a seller declare an availability distribution that names "the unique identifier of an inventory location where quantity is available for the inventory item", the quantity there, and an expected fulfilment time per location. Those are warehouse locations you declare and maintain yourself, and the buyer still sees one number on the listing, so it allocates rather than holds.
The practical consequence is not that anyone literally subtracts Amazon's reserved from Shopify's available. It is that state names look interchangeable and are not, so a mapping written once during implementation, from whatever each system calls "held", quietly produces an available-to-sell figure that is wrong in ways nobody traces back to the mapping.
The definitions move under you
On 5 August 2026, Shopify ran a one-time migration moving inventory held for draft orders, transfers and shipments out of the reserved state into committed, to align those holds "with how order inventory is already represented, so that committed reflects all inventory that is spoken for but not yet fulfilled". Developers relying on reserved "to detect draft-order or transfer/shipment holds specifically" were told to "read committed instead going forward".
Note what that did downstream. No code broke. No error was raised. Queries kept returning numbers.
Be precise about who it hurt, because it is not everyone. If you compute sellable stock by subtracting every held state from on hand, the migration changed nothing: units moved between two states that both sat inside the subtraction. It bites anyone whose logic names particular states — reading reserved to find draft-order holds, or netting some states and not others. That logic kept running and began measuring something else.
The general point outlives the date. The states you integrate against are product decisions owned by someone else and revised on their schedule, and the revision arrives as a changelog entry rather than as an alert inside your system.
What a buffer is actually compensating for
Most multi-channel sellers hold back some stock from some channels. It is worth being honest about which problem that solves.
For a retailer running stores, the first answer is usually not concurrency at all. It is that the on-hand number for a store is not reliable: shrink, mis-scans, returns that have not been processed, damage found at pick, and store picks that fail because the unit is not where the system says. The buffer is absorbing record inaccuracy. Concurrency is the smaller second reason.
That matters because it tells you where to put the buffer. The common instinct is to protect fast movers, but you can only oversell when on hand is near zero, whatever the velocity, and a two-unit buffer against four hundred units does nothing. The exposure sits on thin stock and on locations whose counts you trust least — which is often the slow-moving long tail, precisely because nobody counts it. A flat two units everywhere makes a two-unit product permanently unsellable while leaving the deep fast movers untouched.
The cost is a sum across the products you buffer, not a single multiplication, and it is worth converting to money at cost rather than leaving it in units. Two things belong on the other side of that ledger: the cancellations and marketplace penalties a buffer prevents, and the sales it suppresses, since stock you hide does not sell and a listing that keeps running out loses visibility. Your margin and your own cancellation rate decide it, which is why no benchmark figure appears here.
Wholesale is demand on a different clock
A wholesale order you have accepted is a commitment to deliver a quantity on a date. That is the opposite direction from a purchase order you raise on a supplier, which brings stock in, and confusing the two is how allocation logic ends up double-counting.
Retail and marketplace demand mostly consume stock as it arrives, though not identically: a register sale removes the unit physically and immediately, while a marketplace order reaches you after the fact and can be cancelled inside a window. Wholesale is different in kind. The commitment exists now and the delivery is weeks out.
This is a solved planning problem, not a philosophical one, and it has a name: available-to-promise, computed in time buckets. You net future-dated demand against the receipts scheduled to arrive before that date, rather than against stock on hand today. Done that way, a November order does not starve October, and it does not vanish either.
What defeats most teams is not the arithmetic but the absence of a decision, so the answer varies by whichever system processed the order. Set the policy by product class, channel and customer tier, with a short exception list. Policy per product across a full assortment is a wish, not a plan.
What to do, in order
Write down what each channel currently means by available, from that platform's documentation rather than the label on your dashboard, and reconcile the merchant view against the API view where they differ.
Decide which system owns the available-to-sell calculation, accepting that it will publish numbers rather than place locks, and that store stock and marketplace-held stock have to be modelled differently from warehouse stock.
Point the buffer at thin stock and untrusted counts rather than at velocity, and price both sides of it.
Move wholesale onto time-phased available-to-promise and set the netting policy by class.
Then put real controls under the integration rather than a reminder in a calendar: subscribe to each platform's developer changelog, pin and deliberately migrate API versions, and run a reconciliation check that alerts when a platform's own identity between its states stops holding or when the population of a state shifts materially. That last one would have caught the August change on the day it landed.
Two neighbouring pieces if you want the rest of the model: one source of truth for inventory across stores and marketplaces covers what a single position has to satisfy, multichannel inventory management across Shopify and Amazon covers the two-channel case, and backorder management covers what to do once you have promised something you do not have.
Frequently asked questions
Why do we still oversell when our inventory syncs every few minutes?
Because the gap is not between updates, it is between reading a number and acting on it. Two channels can both read the last unit correctly in the same instant and both sell it, and no polling interval closes that. It is a small share of oversells, since feed lag, throttled calls and unreliable store counts cause more, but it is the share that survives faster syncing.
Should we keep a buffer on every channel?
Not evenly. A buffer mainly absorbs inaccurate on-hand counts rather than concurrency, so point it at thin stock and at locations whose counts you trust least, which is often the slow-moving tail. A flat two units everywhere makes a two-unit product permanently unsellable and does nothing for a product with four hundred on hand. Cost it as a sum across the products you buffer, at cost price.
Does an accepted wholesale order reduce the stock we can sell online?
It should, but not out of today's on-hand figure. Net future-dated demand against the receipts scheduled to arrive before the delivery date, which is available-to-promise computed in time buckets. That way a November commitment does not starve October and does not quietly disappear either. Set the netting policy by product class, channel and customer tier rather than product by product.
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.