New

Odisey — our 30+ module microservice library is now live.

Explore Odisey
Back to Blog
EngineeringMarch 15, 2025

Omnichannel Commerce Engine: From Concept to Production

Building Salefony taught us that true omnichannel isn't just connecting channels — it's a unified data model at the core.

Salesvex Engineering

Engineering Team

7 min read

E
Listen to Article

"Omnichannel" is one of retail's most overloaded words. Vendors use it to mean everything from a mobile app that mirrors the website to a fully unified commerce experience where every channel shares inventory, customer context, and pricing logic in real time.

Building Salefony forced us to define exactly what omnichannel means at the data and infrastructure layer — because the difference between the marketing definition and the engineering reality is where most platforms fail.

What Omnichannel Actually Requires

True omnichannel commerce has three non-negotiable properties:

  1. Unified inventory: A product's available quantity is the same number, everywhere, in real time. If 3 units are available, they're available across web, mobile, POS, and marketplace channels — not 3 available on the website, 3 available in the app, and 3 available in-store.

  2. Unified customer identity: A customer who buys online, returns in-store, and contacts support via chat is the same person to every system. Their complete interaction history is available at every touchpoint.

  3. Consistent pricing and promotions: A promotion active on the web is active everywhere, unless explicitly excluded. Price inconsistencies between channels are a support burden and a brand trust problem.

Most "omnichannel" platforms achieve one or two of these. Achieving all three required us to rethink the data model from the ground up.

The Inventory Problem: Why It's Hard

Inventory management looks simple until you account for the following scenarios that happen every day in retail:

  • A customer adds an item to their cart online (reserved) while another customer picks it up in-store (sold). Which sale completes?
  • A warehouse worker damages 5 units. The system must reflect this before the next batch sync, not after.
  • A flash sale drives 10,000 concurrent add-to-cart events for a product with 500 units in stock.

Traditional inventory systems use periodic batch synchronization between the warehouse management system and each channel. This works at low scale. At high scale, it produces the most damaging failure mode in e-commerce: overselling.

We built inventory management around a distributed reservation model.

Every add-to-cart creates a soft reservation with a 15-minute TTL. The reservation decrements available inventory immediately — not in the next sync. When checkout completes, the soft reservation becomes a hard allocation and triggers warehouse fulfillment. If the TTL expires without checkout, the reservation releases and inventory becomes available again.

The hard part is the distributed consistency problem: with 10,000 concurrent carts, you need a coordination mechanism that prevents two carts from reserving the same unit without creating a serialization bottleneck.

We solved this with optimistic locking at the inventory record level combined with Redis-based reservation counters. The Redis counter provides the fast decrement check; PostgreSQL provides the authoritative record with conflict detection on commit. If a conflict occurs — two concurrent reservations for the last unit — one succeeds and one receives a soft failure, triggering the frontend to show "item no longer available."

In 18 months of production, our oversell rate: 0 incidents.

Customer Identity: The Cross-Channel Graph

Customer identity across channels is a data modeling problem disguised as a technology problem.

A customer interacting with a retail brand might have: an email account on the website, a guest checkout history, a loyalty program membership (with a different email), a social login via Google, and an in-store purchase history tied to their phone number. These are the same person. Connecting them is non-trivial.

We implemented a probabilistic identity graph with deterministic anchors.

Deterministic anchors: verified email addresses, phone numbers, loyalty IDs. Two records sharing a deterministic anchor are definitely the same person and merge automatically.

Probabilistic signals: same device ID, same billing address, similar name + zip code combinations. These signals raise a confidence score. Above a threshold, records merge automatically. Below it, they're flagged for manual review.

The identity graph runs as a separate service, not embedded in the customer record database. Every channel emits identity events; the graph service resolves them and maintains a canonical customer profile. All other services query customer data through the graph, not directly from channel-specific tables.

This separation means adding a new channel — a new marketplace integration, a new POS system, a new social commerce feature — requires only implementing the identity event emission. The resolution logic stays centralized.

Pricing Engine: Rules Without Complexity

Pricing in retail is genuinely complex. You have base prices, promotional prices, bundle prices, loyalty tier prices, channel-specific prices, and time-limited offers. The combinations multiply quickly.

We made an early decision that saved us significant architectural complexity: all pricing rules evaluate against the same engine, regardless of channel.

A pricing rule looks like:

IF customer.loyaltyTier == "Gold"
AND product.category == "Electronics"
AND channel IN ["web", "mobile"]
AND currentTime BETWEEN promotionStart AND promotionEnd
THEN price = basePrice * 0.85

Rules are stored as data, not code. Adding a new promotion means creating a new rule record, not deploying new code. The pricing engine evaluates all applicable rules for a given customer + product + channel combination and returns the lowest qualifying price.

Channel-specific prices are just another rule condition. We don't need separate pricing databases for different channels — we need one rule engine that considers channel as an input.

The Sync Layer: Connecting Legacy Systems

Most retailers building omnichannel capability aren't starting from scratch. They have existing ERP systems, legacy POS software, and established warehouse management systems that weren't built with real-time integration in mind.

Salefony's sync layer handles this reality through a combination of webhooks, polling adapters, and change data capture.

For modern systems with APIs: webhooks deliver real-time events. Inventory updates, order completions, return processing — all flow through a webhook pipeline.

For legacy systems without real-time APIs: we use polling adapters with configurable intervals (as low as 30 seconds for inventory, as high as 4 hours for product catalog updates).

For databases with direct access: change data capture via Debezium streams row-level changes into our event bus without requiring any changes to the legacy application.

The sync layer abstracts all of this behind a unified event model. The omnichannel engine doesn't know or care whether an inventory update came from a webhook, a poll, or a CDC stream.

Production Numbers

Salefony today handles:

  • 8.3 million SKUs across 340 retail clients
  • 2.4 million daily order lines
  • 47 channel types (web, mobile, 12 POS systems, 7 marketplaces, 28 social commerce integrations)
  • Sub-100ms inventory availability checks (p99)
  • 0 oversell incidents in 18 months

The engineering principles that got us here aren't complicated: unified data models, event-driven consistency, and treating channel as an input variable rather than an architectural boundary.

E-commerceOmnichannelEngineering

Try Salesvex

Explore the Salesvex enterprise software platform for free.