
A customer paid for a shirt online, chose collection at the Croydon store, and drove 20 minutes to be told it was not there. The system said 3 in stock. The shelf said none. Somebody had sold the last 3 through the till 90 seconds before the web order landed, and the sync ran every 15 minutes.
The usual conclusion is that the sync is too slow. It almost never is. Omnichannel inventory sync fails because the business has never written down what "available" means, and 6 systems have each guessed differently.
Ask 5 people in a retail business how many units of a SKU are available and you will get 5 numbers, all defensible.
The warehouse says 40, because that is what is on the racking. Finance says 38, because 2 are reserved against an unshipped order. The store says 36, because 2 are in a fitting room and 2 more are damaged. Ecommerce says 30, because somebody set a buffer years ago and nobody remembers why. The marketplace channel says 12, because it was allocated a slice.
All 5 are correct. None of them is availability. Availability is a policy decision the business has to make, and until it is written down, integrating the systems just makes the disagreement faster.

Available to promise is what you are willing to sell right now, per channel and per fulfilment node. Write it as arithmetic and put it in the specification.
A workable starting formula:
ATP = on hand, minus allocated, minus reserved, minus safety buffer, plus inbound within the promise window
Each term is a decision, not a lookup.
On hand is the counted physical quantity at one node. Not the group total. A retailer with 60 stores has 60 on hand numbers and 1 warehouse number, and collapsing them into a single figure is where click and collect starts failing. The group total is a useful merchandising number and a dangerous availability number, because a customer cannot collect from an average.
Node granularity is a design decision with a cost attached. Modelling every store as a node gives accurate promises and 60 sets of buffers to maintain. Modelling regions instead halves the maintenance and reintroduces the averaging problem in a smaller form. Most retailers we work with end up modelling stores individually for click and collect and ship from store, then grouping them for the availability shown on a product page, which is two different questions answered by the same data.
Allocated is stock committed to orders already accepted. Reserved is stock held in a basket or a pick queue and not yet sold, which is a different thing and needs its own expiry.
Safety buffer is the honest admission that your count is imperfect. A store with quarterly counts and no RFID might carry a buffer of 2 units on fast movers. A warehouse with weekly cycle counts might carry zero. Setting one global buffer of 5 across every node is the most common configuration mistake we see, and it strands stock everywhere while still overselling in the 2 or 3 places that needed a bigger number.
Inbound within the promise window is what turns a 5 day lead time into a sellable proposition. If a purchase order lands on the 4th and you promise dispatch by the 6th, that stock is sellable on the 2nd, and choosing not to count it costs real revenue.
Write this formula once per channel. Web, marketplace, B2B portal and store all get different buffers, because they have different tolerance for disappointing a customer. A marketplace suspension for late dispatch is far more expensive than an apology on your own site, so the marketplace buffer should be larger. That asymmetry is a commercial decision and it belongs to the business, not to the integration team.
Phantom inventory is the count saying yes when the shelf says no, and it comes from 4 places.
1. Unrecorded shrink. Theft, damage and misplacement. Retail shrink runs at meaningful percentages of sales across the industry and no software fixes it, but cycle counting bounds it. Counting the top 200 SKUs weekly and the long tail quarterly catches most of the value at a fraction of the labour of a full stock take.
2. Unit of measure errors. A case of 12 received as 1 unit, or an each sold against a case barcode. This is a master data problem masquerading as an inventory problem, and it is the single most common cause of a count that is wrong by exactly a case pack.
3. Failed messages nobody looked at. A webhook that returned 500 and was never retried. An overnight batch that skipped 400 rows because 1 row had a bad character. The count does not look wrong, it looks slightly stale, and it stays that way until somebody counts. This is the drift source that scales with integration count, which is why the retail vertical prices integration quality rather than integration quantity: every additional system adds contract tests, failure paths and another place for a message to die quietly.
4. In flight stock with no owner. Units in transit between stores, on a returns pallet, or sitting in a fitting room. Each is real stock in an undefined state, and states you have not modelled become states your count gets wrong.
Only the first is genuinely a physical problem. The other 3 are software problems with software fixes, which is why a reconciliation report belongs in scope as a deliverable rather than a phase 2 nicety.

A reservation holds stock between the moment a customer commits and the moment the order is confirmed. Get it wrong in either direction and it hurts.
Hold too briefly and 2 customers buy the last unit during a payment redirect that took 40 seconds. Hold too long and abandoned baskets strand your best sellers for hours. A 15 minute time to live on checkout reservations is a common starting point, with a shorter 5 minute hold during flash events where basket abandonment spikes.
Every reservation needs 3 properties. An expiry, so nothing is held forever. An owner, so you can trace why a unit is unavailable. And a compensating action, so that when the expiry fires the stock returns to availability and the channel is told. Reservations that expire silently without republishing the count are how a retailer ends up with 200 units of phantom unavailability on a Monday morning.
The same logic applies to store picks. A click and collect order that a store has 2 hours to pick is a 2 hour reservation, and if the pick fails the compensating action has to reroute the order rather than cancel it.
Reservations also need to survive a restart. Holding them only in memory works perfectly until a deployment at 11am on a Thursday releases 400 units that customers believe they have bought. Persist them, key them to the basket or order, and let the expiry be a stored timestamp rather than a running timer. This sounds obvious written down and it is one of the more common defects we find in systems that have otherwise been built carefully.
One more asymmetry worth designing for. A reservation that expires too early costs you a single order. A reservation that never expires costs you that unit for as long as the system runs, and those losses accumulate silently across thousands of abandoned baskets. When in doubt, expire aggressively and let the customer re-add.
Not everything needs to be instant, and pretending otherwise is expensive.
|
Flow |
Latency that works |
Why |
|
Sale at POS reducing web availability |
Under 60 seconds |
This is the oversell path |
|
Web order reducing store availability |
Under 60 seconds |
Same reason, other direction |
|
Warehouse receipt increasing availability |
5 to 15 minutes |
Nobody is harmed by selling it slightly late |
|
Marketplace channel allocation |
5 to 15 minutes |
Channel APIs rate limit anyway |
|
Full reconciliation sweep |
Nightly |
Catches everything the event stream missed |
|
Cycle count adjustments |
On completion |
Human paced by nature |

Two paths, not one. An event stream for the paths where seconds matter, and a scheduled reconciliation that compares totals and reports variance. The reconciliation is not a backup for the event stream. It is the thing that tells you the event stream is broken, and a build without it will run wrong for months without anyone noticing.
Idempotency matters more than speed here. Every inventory message needs a key so that a retry does not double count, because retries are guaranteed and duplicate decrements are almost impossible to unpick after the fact.
Published rates, published hours, arithmetic you can argue with.
|
Component |
Hours |
Note |
|
ATP definition and rules engine |
180 to 340 |
Per channel and per node, with buffers |
|
Reservation service with expiry |
120 to 220 |
Includes compensating actions |
|
Event stream and idempotent handlers |
200 to 380 |
The path where seconds matter |
|
Each system integration |
60 to 160 |
Documented API low, file drop high |
|
Reconciliation and variance reporting |
140 to 260 |
The deliverable that gets cut and should not be |
|
Store fulfilment app for pick and handoff |
290 to 680 |
Handheld, offline tolerant |
The retail vertical's own phase one arithmetic sets the frame: a lean slice runs 850 build hours plus 170 QA at 20 percent plus 60 DevOps, so 1,080 hours, about $65,000 at $60. A full slice runs 1,300 build plus 325 QA at 25 percent plus 120 DevOps, so 1,745 hours, about $122,000 at $70. Rates are $40 to $100 per hour by role, and mixed teams blend to $60 to $70.
One scheduling constraint that is not negotiable in retail. Put a mid October to mid January freeze in the contract. A 3 week slip on an inventory cutover lands on peak trading, and neither the slip nor the peak will move.
1. A single global buffer. Covered above and worth repeating, because it is in almost every system we inherit.
2. Go live scheduled into peak. See the freeze. This one is entirely self inflicted.
3. Product data not ready. Orphaned variants, missing case pack conversions, no barcode discipline. Gate the build on a data readiness report and name a taxonomy owner on the client side, because the integration team cannot fix a catalogue they do not own.
4. Sync treated as bidirectional everywhere. Decide which system is the source of truth for each field and enforce it. Two systems both authoritative for on hand quantity will oscillate, and the oscillation looks like drift.
5. Store staff blamed for routing decisions. A pick failure rate of 8 percent is usually the routing engine sending orders to a store with 1 unit and a buffer of 0, not an associate who cannot find a shirt. Publish the routing rules where the store can read them.
6. No oversell tolerance agreed. Zero oversell is achievable and it costs a fortune in stranded stock. Pick a number, defend it, and design to it.
Everything above is theory until a store colleague is asked to find one shirt in a stockroom during a Saturday rush.
Ship from store and click and collect both take a warehouse concept, allocation, and hand it to a person who has a queue at the till. That changes what the routing engine should optimise for. Sending an order to the nearest store with stock is the obvious rule and frequently the wrong one, because the nearest store may be the busiest, the most understaffed, or the one whose count has been drifting since the last cycle count.
Better routing weighs 4 things: distance to the customer, count confidence at that node, current pick load at that store, and margin impact of splitting the order across nodes. A store at 92 percent count accuracy with 30 open picks should not receive the last unit of anything, no matter how close it is.
Design to the scan sequence as well. A picker paid on rate will scan at the end of the aisle and share logins if the handheld adds keystrokes, which is the same adoption failure that shows up in warehouse builds. If the app requires 6 taps to reject a line as not found, colleagues will mark it found and cancel it later, and the count stays wrong.
Two practical rules worth adopting. Give the store a way to reject a pick that reroutes rather than cancels, because a cancellation is a lost sale and a reroute is a slightly later one. And make short pick reporting a one tap action that immediately adjusts the count at that node, because the person standing in front of the empty shelf is the most reliable stock counting device in the business and most systems give them no way to say so.
Sync latency is an engineering metric that tells the business nothing. Three numbers matter.
Oversell rate, as a percentage of orders that cannot be fulfilled from the node they were promised against. Set a target, publish it, and treat a breach as a defect rather than a fact of life. Under 1 percent is a reasonable ambition for most multi store retailers, and getting to 0.2 percent costs considerably more than getting to 1.
Click and collect pick success, the percentage of store picks completed in full within the promised window. This is the number that exposes buffer settings faster than any report.
Count accuracy by node, from cycle counts. If one store sits at 92 percent while the estate averages 98, that store needs a bigger buffer today and an investigation this month. Buffers set from measured accuracy rather than from a default are the single cheapest improvement available to most retailers, and they cost nothing but the discipline to look at the number.
All 3 belong on the same weekly report, because they trade against each other. Push buffers up and oversell falls while stranded stock and lost sales rise. Push them down and the opposite happens. A business that sees only one of those numbers will optimise it into the ground.
Before anyone opens an API document, write one sentence: for this channel, at this node, we will sell a unit when this formula says so, and we accept this oversell rate. Get that agreed and the integration is ordinary work. Skip it and no amount of sync frequency will save the project.
The quantity you are willing to sell right now for a given channel and fulfilment node. A workable formula is on hand, minus allocated, minus reserved, minus a safety buffer, plus inbound arriving within the promise window. Each term is a business decision rather than a database lookup, and the buffer should differ per channel because a marketplace suspension costs more than an apology on your own site.
It depends on the path. Anything on the oversell path, such as a POS sale reducing web availability, should land in under 60 seconds. Warehouse receipts and marketplace allocations are fine at 5 to 15 minutes. A full reconciliation sweep runs nightly and exists to catch what the event stream missed.
Four things. Unrecorded shrink from theft, damage or misplacement. Unit of measure errors, such as a case of 12 received as a single unit. Failed messages that were never retried. And in flight stock in states nobody modelled, such as returns pallets or fitting rooms. Only the first is a physical problem.
Define ATP per channel and per node, set safety buffers that reflect each node's real count accuracy rather than one global number, give reservations an expiry with a compensating action, and run a nightly reconciliation that reports variance. Then publish oversell rate as a measured target instead of treating it as unavoidable.
A lean phase one slice runs about 1,080 hours including QA and DevOps, roughly $65,000 at a $60 blend. A fuller slice runs about 1,745 hours, roughly $122,000 at $70. Rates are $40 to $100 per hour by role depending on whether the work is front end or integration architecture.
Only where you have decided it should be. Each field needs one authoritative system. If two systems are both authoritative for on hand quantity they will oscillate, and the oscillation is usually misdiagnosed as drift.
A 15 minute time to live is a common starting point, dropping to about 5 minutes during flash events when basket abandonment rises. Whatever you choose, the reservation needs an expiry, an identifiable owner, and a compensating action that returns the stock to availability and republishes the count.

Vikas has around fifteen years of experience building software and now builds generative AI systems at Zyneto. His work covers retrieval augmented generation, agentic AI, knowledge graphs, AI memory, and the evaluation and guardrails that decide whether any of it is safe to put in front of customers. He has shipped enterprise copilots, document AI, chatbots and predictive analytics for e-commerce, fintech and marketing teams, and works day to day in Python, JavaScript and SQL. He follows multimodal models, business process automation and enterprise AI security closely, and mentors engineers moving into AI. He writes about architecture, inference cost and the failure modes that only show up at production scale.
Share your details and we will talk soon.
Be the first to access expert strategies, actionable tips, and the trends actually shaping the digital world. No fluff - just practical insights delivered straight to your inbox.
Dive into our blog and stay ahead of the curve with expert perspectives, future-ready trends, and tech tips written for decision-makers and doers alike.