Skip to main content
Back to AI Commerce Lab
Operations·April 2025·13 min read

From Clicks to Bricks: How Technology is Powering Omnichannel Retail

Omnichannel retail breaks at exactly one seam: the moment the store's till and the website's cart disagree about how much stock exists. Every downstream symptom, a canceled buy-online-pickup-in-store order, an oversold marketplace listing, a phantom "in stock" badge, traces back to that single divergence.

The one problem this architecture actually solves

"Omnichannel" gets sold as a customer-experience initiative: consistent branding, synced loyalty points, a nice app. The actual engineering problem underneath it is narrower and harder: one product, sold through a store register, a website, and one or more marketplaces, has to have exactly one number representing how many units exist and where they are, at any given moment.

None of the customer-facing features that get pitched as "omnichannel", buy-online-pickup-in-store, ship-from-store, cross-channel returns, are actually new capabilities. Every one of them is a routing decision that already existed inside a single channel; what's new is making that decision safely when three or more channels can claim the same physical unit at once.

Get that number wrong and every other feature built on top of it, ship-from-store, curbside pickup, cross-channel returns, inherits the error. This piece is about the architecture that keeps that number honest, not the customer-facing features layered on top of it.

A single identifier scheme across every channel

Unified inventory is impossible if the store's SKU, the website's product ID, and the marketplace's listing ID don't resolve to the same underlying item. The GS1 Global Trade Item Number, a barcode-encoded identifier standardized for use across the entire retail supply chain, is what makes that resolution possible without a manual mapping table per channel (GS1 — Get barcodes).

Why marketplaces and stores fail without a shared key

A marketplace integration built against an internally generated SKU, instead of the GTIN, works fine until a second marketplace, or a POS upgrade, needs the same product mapped differently. Every additional channel then needs its own translation layer, and those translation layers are where inventory numbers quietly drift apart.

The fix isn't clever software. It's discipline: one canonical identifier, assigned once, referenced by every system, with the GTIN as the join key between the product catalog, the POS, the e-commerce platform, and every marketplace feed.

Variants need their own identifiers, not a shared one

A T-shirt in three sizes and four colors isn't one item with attributes attached in some systems and twelve separate items in others. GS1's own barcode guidance is that each sellable variant, each distinct size-color combination a customer can actually purchase, gets its own GTIN, precisely so a size medium in blue and a size large in black never collapse into the same inventory count (GS1 — Get barcodes).

Skipping this and tracking variants as attributes bolted onto a single parent SKU is one of the most common causes of oversells in a growing catalog: the parent count looks healthy while a specific, popular variant has actually been gone for days.

Real-time inventory visibility

A shared identifier only solves half the problem. The other half is knowing, in near real time, what happened to a unit with that identifier, sold, received, moved, returned, damaged, across every location.

EPCIS as the event model for "what moved, where, when"

GS1's Electronic Product Code Information Services standard defines a common event model for exactly this: what happened, where it happened, when it happened, and why, shared as a structured event rather than a periodic batch export (GS1 — EPCIS). A sale at a register, a pick from a distribution center shelf, and a customer return at a different store from the original purchase all become the same event shape, which is what makes them queryable against one inventory truth instead of three disconnected systems.

The GS1 Global Traceability Standard extends the same idea across the full product lifecycle, defining critical tracking events and the key data elements attached to each one, so a unit's history is reconstructable end to end (GS1 — Traceability).

Inventory visibility isn't a dashboard. It's an event log with a shared identifier and a shared event shape, queried by whatever dashboard or allocation engine needs it.

Soft allocation: reserving stock before the order is final

A shopper adding an item to a cart hasn't bought it yet, but the last unit in a location still needs to be provisionally held so two shoppers don't both reach checkout believing they'll get it. Soft allocation reserves a unit against a cart for a short, expiring window, releasing it back to available stock automatically if the cart doesn't convert.

Get the expiry window wrong in either direction and it shows up as a customer complaint: too short, and a slow checkout loses the reservation and the sale; too long, and abandoned carts quietly starve other channels of stock that was never actually going to sell.

The order orchestration layer

Real-time stock visibility answers "what do we have." Order orchestration answers the harder question: "which unit, at which location, fulfills this specific order," across every channel simultaneously, without two channels claiming the same unit.

What a distributed order management system actually does

A distributed order management system centralizes order visibility and control across channels, and handles inventory availability checks, order promising, and dynamic routing to whichever fulfillment source, store, distribution center, or supplier, can actually service a given order (Manhattan Associates — Order Management System). That routing decision has to happen against the same real-time stock truth described above, not a nightly inventory snapshot.

"Order promising" is the specific term for the step that matters most operationally: deciding, at the moment of checkout, what delivery date the system can actually commit to for a given fulfillment path. Promise a date the chosen store or DC can't actually hit, and the architecture has technically prevented an oversell while still producing an angry customer.

Idempotency: the detail that prevents double allocation

Every order write in a distributed system needs to survive a retry without allocating the same unit twice or charging a customer twice. Stripe's own API design solves this with an idempotency key: a client-generated identifier attached to a request, so a retried request with the same key returns the original result instead of executing the operation again (Stripe — Idempotent requests). The same principle has to apply to order and allocation writes, not just payment writes, or a network retry during a spike becomes a double-shipped order.

POST /orders
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7

{
  "gtin": "00012345678905",
  "quantity": 1,
  "channel": "marketplace-a",
  "fulfillment_hint": "nearest-store"
}
STORE POS, in-aisle WEB storefront, app MARKETPLACE 3rd-party listings UNIFIED LEDGER Real-time stock truth GTIN-keyed, EPCIS events Order orchestration & allocation idempotent writes SHIP FROM STORE DC FULFILLMENT warehouse pick/pack BOPIS / CURBSIDE orders + stock checks allocation decision fulfillment status updates stock truth

Store, web, and marketplace orders all resolve against one ledger before a fulfillment source is chosen; fulfillment status flows back to keep stock truth current.

One order, traced through the architecture

A customer buys a jacket, size large, through a third-party marketplace listing. The marketplace channel checks the ledger by GTIN, sees the large is available at two locations, a distribution center and a nearby store, and soft-allocates the unit at the store since it's closer to the delivery address.

The order write carries an idempotency key. A network blip during checkout causes the marketplace's system to retry the request automatically; the ledger recognizes the repeated key and returns the original allocation instead of reserving a second unit for the same order.

Store staff fulfill it as a ship-from-store order, and the POS terminal's fulfillment action writes a decrement event back to the same ledger the marketplace checked at checkout, not a separate store-only system. Three weeks later, the customer returns the jacket to a different store entirely; that store's POS logs a return event against the same GTIN, and the unit is back in available stock everywhere within the same real-time window a sale would have used.

Nothing in that sequence required a human to reconcile a spreadsheet. Every step read from and wrote to one ledger, using one identifier, with one mechanism preventing duplicate writes.

Fulfillment options this architecture makes possible

Once the ledger and orchestration layer exist, fulfillment stops being a single fixed path (warehouse to customer) and becomes a routing decision made per order.

Ship from store

Shopify's own POS documentation describes ship-from-store as letting store staff complete fulfillment, printing packing slips and shipping labels, directly from the point-of-sale terminal, turning a store's on-hand inventory into an active fulfillment source for online orders (Shopify — Ship from store on Shopify POS). This only works if that store's on-hand count is accurate in the same ledger the website reads from.

Buy online, pick up in store

BOPIS routes an online order to a specific store for in-person pickup, and depends on the same real-time accuracy: a customer arriving to find the item isn't actually there is the single fastest way to erode trust in an omnichannel program (Shopify — BOPIS guide).

Fulfillment optionTriggerInventory sourceTypical failure mode
Ship from storeOrder placed online, nearest store has stockStore on-hand countStore count stale relative to POS, order fails after promise
DC fulfillmentOrder placed online, DC has stockWarehouse management systemPick/pack delay not reflected back to promised delivery date
BOPIS / curbsideCustomer selects a specific store at checkoutStore on-hand count, reserved at order timeReservation not honored against a concurrent in-store sale
Marketplace fulfillmentOrder placed on a 3rd-party marketplaceWhichever source the ledger allocatesMarketplace feed lag causes an oversell the ledger already prevented internally

Returns are an inbound inventory event, not a separate workflow

A customer returning a web order at a physical store is, from the ledger's point of view, exactly the same kind of event as a store receiving a shipment: a unit re-enters available stock at a specific location, at a specific time. GS1's traceability model treats a return the same way it treats any other critical tracking event, tied to the same GTIN, the same location codes, and the same event shape as every other movement in the unit's history (GS1 — Traceability).

Retailers that bolt returns processing onto a separate system, disconnected from the ledger, end up with the most common cross-channel failure: a returned unit that's physically back on a shelf but invisible to every channel until the next manual stock count catches up.

Where this breaks in practice

Negative and ghost inventory

Ghost inventory is stock the system believes exists but that isn't physically available, usually the result of a write that updated one channel's count but not another's. It shows up as a product marked in stock everywhere, that's actually sold out at the only location the ledger checked.

Marketplace sync lag

A marketplace feed that syncs on a schedule, even an aggressive one, introduces a window where the marketplace believes stock exists that the ledger has already allocated elsewhere. The fix isn't a faster sync interval, it's the marketplace channel reading and writing against the same real-time ledger every other channel uses, not a periodically refreshed copy of it.

Channel-specific rules on top of shared truth

A shared inventory ledger doesn't mean every channel sells identically. A marketplace listing might carry a different price or a promotional restriction than the same GTIN on the website, and a store might hold back a display unit that's technically in stock but not sellable.

Those are business rules layered on top of the ledger's stock count, applied at allocation time, not separate inventory numbers. Conflating "not sellable through this channel right now" with "doesn't exist" is how a display unit or a marketplace-restricted item turns into a phantom stockout everywhere else.

  1. Establish the GTIN as the canonical identifier across catalog, POS, e-commerce, and every marketplace feed before building anything else.
  2. Stand up the real-time inventory ledger, fed by EPCIS-shaped events from every location, before turning on any new fulfillment option.
  3. Add idempotent order writes to the orchestration layer before enabling ship-from-store or BOPIS, since these are exactly the flows that create concurrent claims on the same unit.
  4. Bring marketplaces onto the same ledger last, since their sync behavior is the hardest to control and the most likely source of an oversell.

The store, the website, and the marketplace are not three inventory systems that need to agree. They're three views onto one ledger that was never supposed to have three copies in the first place.

Who owns this once it's running

A unified ledger doesn't stay accurate on its own. It needs an explicit owner, and the ownership question usually falls between two teams that don't naturally talk to each other: store operations, who control the physical count, and e-commerce engineering, who control the digital one.

Data quality as an ongoing operational duty

Store-level stock counts drift for mundane reasons: a damaged item pulled from the shelf without a system write, a miscount during a cycle count, a transfer between stores logged a day late. None of these are software bugs, but all of them corrupt the ledger the same way a bug would.

Someone has to own reconciling physical counts against the ledger on a regular cadence, and that ownership needs to be a named team with an SLA, not an implicit assumption that store staff will catch every discrepancy during a busy shift.

Monitoring the seams, not just the ledger

The riskiest failure points in this architecture are the seams between systems, the POS-to-ledger write, the marketplace feed sync, the returns event, not the ledger's own database. Alerting on sync latency and failed writes at each seam, the same discipline used to monitor any other distributed system's integration points, catches ghost inventory before a customer does.

FAQ

Do we need a full distributed order management system to start, or can we grow into one?

The ledger and a single canonical identifier come first. A full DOM platform's routing and order-promising logic (Manhattan Associates — Order Management System) matters once more than one fulfillment source needs to compete for the same order, which is usually after the identifier and ledger work is already done.

What's the actual difference between a nightly inventory sync and real-time visibility?

A nightly sync is a snapshot that's already stale the moment a sale happens anywhere in the intervening 24 hours. Real-time visibility, built on an event model like EPCIS, updates the shared truth as each event happens, not on a batch schedule (GS1 — EPCIS).

Why does idempotency matter for orders specifically, not just payments?

A retried order-allocation request, caused by a network timeout or a client retry, without an idempotency key can allocate the same unit of stock to two different customers. The same mechanism that prevents Stripe from double-charging a card prevents an order system from double-allocating a unit (Stripe — Idempotent requests).

Is ghost inventory a data problem or a process problem?

Both. The immediate cause is usually a missed or delayed write between systems, but the underlying cause is almost always more than one system holding its own copy of a count that should have a single source of truth.

Should marketplaces be integrated first or last?

Last. Marketplace feeds are the hardest sync behavior to control, since the marketplace, not the retailer, often dictates the sync interval and retry behavior, making it the worst place to discover a gap in the ledger's real-time accuracy.

Do product variants really need separate identifiers, or is that overkill for a small catalog?

GS1's own guidance applies regardless of catalog size: each sellable variant gets its own GTIN (GS1 — Get barcodes). A small catalog just means the cost of retrofitting this later, once oversells start happening on specific variants, is smaller than it would be at scale, not that the underlying problem doesn't exist.

Who should own the unified ledger, store operations or e-commerce engineering?

Neither team alone. The ledger sits between both, which is exactly why it needs an explicit, named owner with a reconciliation SLA, rather than being treated as e-commerce's problem when it breaks on the store side or store operations' problem when it breaks online.

References

From the Destm engineering archive. For current work on this topic, start at Solutions or the blog.