The order that breaks an OMS is never the normal one. It’s the order placed four seconds before a delivery app goes down, right as the kitchen prints a ticket for an item that just sold out in the dining room, on the one terminal whose Wi-Fi decided to drop. We’ve built order management systems for restaurant groups and multi-channel retailers who came to us after exactly this kind of order caused a real incident — a double-fulfilled order, an oversold item, a ticket that never reached the kitchen. That’s just Tuesday.
An OMS that only works when every channel (POS, mobile app, delivery platform), every network connection, and every downstream service behaves is a demo, not a production system. This is our field-tested view on architecting order management that holds up under the two conditions that actually define restaurant and retail order flow:
- Multiple channels selling from the same inventory at once
- Networks that are never as reliable as the architecture diagram assumes
What an OMS Actually Has to Do
An order management system is the orchestration layer that takes an order from any channel; POS (point of sale), app, website, kiosk, phone, or a third-party delivery platform, and coordinates every downstream system that has to react to it: inventory, payment, kitchen or warehouse fulfillment, delivery dispatch, and customer notification. It is not simply a database table of orders; it is the thing deciding what happens next, in what order, and what to do when a step fails.

For restaurants, channel diversity is extreme by nature: an in-house POS, an online ordering page, a handful of third-party delivery platforms, self-service kiosks, and phone orders can all funnel into the same kitchen within the same minute. For multi-channel retailers, the shape of the problem is identical even though the vocabulary differs; orders from a mobile app, a website, a marketplace listing, and an in-store POS are all competing for the same finite inventory pool. Both cases need the same architectural answer.

The Core Architecture: Event-Driven, Not Request-Response
Instinctive First Design: Every service calls every other service directly and waits for a response. It works fine in a demo with one channel, but breaks down fast once a second and third channel start writing to the same inventory and order state at the same time.
What Really Works: Event-driven architecture is the standard answer: producers emit events like OrderPlaced, PaymentAuthorized, or InventoryReserved onto a shared channel, and each downstream consumer reacts independently instead of being called synchronously by every upstream service.
Many teams pair this with event sourcing and CQRS (Command Query Responsibility Segregation); every state change to an order is stored as an immutable event, and the order’s current state is derived by replaying that event history rather than being overwritten in place. For restaurants and retail, this earns its complexity fast: refund disputes, missing-item claims, and “why does the system say this order is still open” questions all get answered by reading the order’s actual event timeline instead of guessing from a single mutable row.
That said, we don’t reach for full event sourcing on every project by default. A well-designed state machine combined with a transactional outbox pattern (writing the state change and its event together in one atomic step, then publishing the event separately) is often enough for a single-location or small multi-channel operation; the full event-sourced approach earns its keep once the number of channels, locations, or dispute volume makes “what actually happened to this order” a recurring, expensive question.
Orchestration and the Saga Pattern
Problem: Because a single order touches several independent services — inventory, payment, fulfillment — that can’t share one database transaction, you need an explicit way to handle the case where step three fails after steps one and two already succeeded.
Solution: Enter the Saga Pattern. This is what the saga pattern solves: a sequence of local transactions across services, with a defined compensating action for every step that might need to be undone.
Sagas can be implemented through choreography, where each service listens for events and decides its own next move, or through orchestration, where a central coordinator issues commands and tracks the flow explicitly. We lean toward orchestration for order flows specifically, because restaurant and retail ops teams need one place to answer “why is order #4821 stuck,” not an implicit web of independent event listeners that requires tracing through five services to reconstruct what happened.
Idempotency (the guarantee that repeating the same operation produces the same result as doing it once) has to be part of this from day one, not bolted on later. Networks retry. Delivery platform webhooks (automated callback calls one system sends another when an event happens) retry. A payment confirmation can arrive twice. We’ve personally chased down a duplicate-kitchen-ticket bug that traced back to a delivery aggregator retrying a webhook call that wasn’t idempotent on our end — the ticket printed twice, the kitchen made two of an item that was ordered once. Every step in the saga has to be safe to execute more than once with the same input.
Preventing Oversells Across Channels
Problem: The same SKU (stock-keeping unit) is often being sold in-store, in an app, and across several delivery platforms simultaneously, all drawing against one inventory count. Reading a stock count and writing back a decremented value in two separate steps is a race condition waiting to happen the moment two channels sell the last unit within the same second.
Solution: The fix is an atomic, conditional write; a single operation that decrements stock only if enough stock exists, rather than a read followed by a write. Layered on top of that, short-lived reservations at the point a cart is created (not just at checkout) protect high-demand items during the window between “added to cart” and “payment confirmed,” with an automatic release if the reservation times out unclaimed.
For restaurants, this same problem shows up as 86’ing an item; marking it sold out. The moment the kitchen marks an item unavailable, every channel needs to reflect that in real time, not on the next menu sync cycle. A customer completing a delivery order for something the kitchen ran out of ten minutes ago isn’t a data-freshness inconvenience; it’s a customer-facing failure and a refund you didn’t need to issue.
Designing for the Network You Actually Have
A restaurant or retail store cannot simply stop taking orders because an internet connection blipped. This is the case for a local-first architecture: the POS and kitchen display system keep operating against a local data store during an outage, queuing orders and printing tickets on the local network, and syncing everything back to the cloud the moment connectivity returns.
The harder question is what to do about conflicts once two devices that were both operating offline reconnect and disagree; two terminals processing a return on the same item, for instance. Conflict-free replicated data types (CRDTs) are the academically clean answer, and they’re worth reaching for if your consistency requirements genuinely demand it. In practice, for most restaurant-scale and mid-size retail operations, we’ve found a simpler reconciliation job — flagging conflicting writes for a clear, deterministic resolution rule instead of building CRDT-grade infrastructure; gets the same outcome with far less engineering overhead. Change data capture (CDC) streaming from edge devices into a central event bus like Kafka (a distributed event-streaming platform) is usually the more valuable investment: it gives you a real-time feed of what happened at the edge without forcing every edge device to be a fully distributed database node.
Delivery Aggregator Integration Without Losing Your Mind
Problem: Every third-party delivery platform has its own API shape, its own webhook payload format, and its own quirks around menu and availability sync. The mistake we’ve seen, and made ourselves, early on; is letting one platform’s order format leak directly into the core order model. Every new platform integration then means touching core logic, and every platform’s edge cases start contaminating each other.
Solution: The fix is a normalization layer: an adapter per external platform that translates its native order and webhook format into one internal, canonical order event before it ever reaches the OMS core. The OMS core only ever needs to understand one order shape, no matter how many delivery platforms, marketplaces, or POS vendors sit in front of it. Adding a new channel becomes “write one more adapter,” not “extend the core order model again.”
Observability: Knowing Where an Order Is, Always
Every order-state transition should be traceable end to end. A support agent or store manager should be able to answer “where is this order right now” in seconds by looking at one aggregated timeline keyed by order ID, not by asking an engineer to grep logs across five separate services. We build this as a first-class view backed by the same event stream driving the orchestration layer, and we monitor the health of that event stream itself — queue depth, consumer lag, failed compensation actions; with the same rigor we’d apply to any other production-critical service.
Lessons We’d Pass On
- Treat inventory reservation as its own bounded concern. Bolting it on as a side effect of order creation is how oversells happen under real concurrent load.
- Orchestrate the flows your ops team needs to debug by hand. Choreography is elegant in a diagram and painful at 2 a.m. when nobody can trace why an order is stuck.
- Idempotency stops being optional the moment you have more than one channel retrying webhooks. Design every step to tolerate being run twice from the start.
- Don’t reach for CRDT-grade offline conflict resolution by default. Confirm a simpler reconciliation process genuinely isn’t enough before building the more complex system.
A Practical Architecture Checklist
- Every order-state transition is an event with a unique, idempotent identifier
- Inventory decrements use atomic conditional writes, not read-then-write
- Reservation timeouts exist and release abandoned holds automatically
- POS and kitchen display systems can operate and queue orders locally during a connectivity outage
- Third-party delivery and channel integrations are normalized through an adapter layer before reaching the OMS core
- A single order-timeline view aggregates every state transition for support and ops visibility
- Compensation logic exists for every orchestrated step that can fail after a prior step already succeeded
FAQ
What’s the difference between an OMS and a POS?
A POS handles the transaction at a single point of sale — payment and ticket printing at one register or kiosk. An OMS sits above every channel a business sells through and coordinates what happens after an order is placed, regardless of whether it originated at a POS, an app, a website, or a delivery platform.
Why use event-driven architecture instead of a simpler request-response OMS?
Event-driven architecture lets each downstream system — inventory, payment, kitchen, delivery; react to an order independently instead of being tightly coupled through synchronous calls, which keeps the system resilient when one channel spikes in volume or one service is temporarily slow.
How do you prevent overselling the same item across multiple channels?
Atomic conditional inventory updates — decrementing stock only if sufficient stock exists in the same operation — combined with short-lived reservations during checkout, rather than reading a stock count and writing back a new value in two separate steps.
What happens to order taking when the internet goes down?
A well-architected POS and kitchen display system keep operating against a local data store during an outage, queueing orders and syncing them back to the cloud once connectivity returns, so service doesn’t stop because a connection blipped.
Should every OMS use the saga pattern?
Any OMS where a single order touches multiple independent services that cannot share one database transaction benefits from a saga-style approach with explicit compensating actions for the step that fails after previous steps have already succeeded.
Exper Labs builds order management and fulfillment systems for restaurants and multi-channel retailers, from POS and kitchen display integration to inventory orchestration across delivery platforms. If you’re seeing cracks in how your orders flow across channels, talk to our engineering team.
.png)


