The challenge
A regional distributor selling through both physical shops and an online store was running two independent stock records. The point-of-sale system in each shop knew what had been sold over the counter, the online store knew what had been ordered on the web, and neither knew about the other until someone exported a report and updated the opposite system by hand, usually the following morning.
The consequences fell on customers. An item sold in a shop stayed purchasable online for hours, producing orders the distributor could not fulfil and had to cancel and refund. Conversely, staff held back stock from the shop floor out of caution because they could not tell how much of the on-hand quantity was already committed to unshipped web orders. Product data drifted too: a price or description changed in one system stayed unchanged in the other.
The distributor did not want to replace the point-of-sale systems the shops already knew how to use. They needed the two worlds connected reliably, including through the network outages that a shop connection occasionally has.
Our approach
We built an integration layer that owns the authoritative stock position and treats both the point-of-sale systems and the online store as participants rather than as sources of truth to be merged. Rather than synchronizing quantity numbers — which is where most of these integrations break — the layer synchronizes stock movements: a counter sale and a web order are both movement events applied to the same ledger, so the two systems cannot drift into disagreeing totals because neither is keeping its own.
Availability is computed as on-hand minus committed rather than as a raw count, so quantity reserved by an unshipped web order is visibly unavailable to the shop floor instead of being informally held back. Every inbound event carries an idempotency key and is processed through a durable queue, so a shop that loses connectivity queues its movements locally and replays them on reconnection without double-applying anything — the common failure mode when a retry has no way to recognize an event it has already seen.
Product data was given a single owner. Prices, descriptions, and attributes are maintained in one place and pushed outward, so the two systems cannot disagree about what a product is or costs. Events that cannot be applied cleanly — a movement for an unknown SKU, a sale that would push a position negative — are routed to an exception queue for a person to resolve, with the original payload retained, rather than being silently dropped or retried forever.
Outcome
The shops and the online store now work from one stock position updated as sales happen, rather than two records reconciled by an overnight export. Web orders can no longer be accepted against stock already sold over the counter, and product prices and descriptions are maintained once instead of in two places that drift apart.


