Skip to main content

Integration pattern

Reviewed 14 October 2026

A faster sync still oversells at the same moment.inventory sync race condition

Each channel keeps its own count and syncs now and then. Two orders for the last unit can both go through before either sale arrives.

The demo posts a stock count in ERPNext. It does not show a store and an ERP disagreeing.

Copies are fine when

  • Stock is deep: the units in stock far outnumber the orders that arrive inside one sync lag.
  • A channel must keep selling when the source is unreachable, such as a shop counter that is offline.
  • A late cancel or a backorder costs less than a lost sale.

Use one count when

  • Units are few or one of a kind: two orders inside the lag can both take the last one.
  • An oversell cannot be undone: a made-to-order or allocated item, or a customer you cannot refund quickly.
  • You sell on one channel, or every channel already reads one inventory, as Medusa sales channels do through their linked stock locations.

Try it

Oversell simulator

One unit, two channels, two orders. Choose how the channels stay in step, how far apart the orders are and how long a sale takes to reach the other channel. Sample data. Not a measurement.

How the channels stay in step

The second order arrives

Sync lag: how long a sale takes to reach the other channel

For a copy synced on a schedule the wait is anywhere from nothing up to the lag. For a push it is the delivery time.This design does not use a lag.

AChannel ABChannel BAB
  1. AChannel A
  2. BChannel B

What happens

  1. 0 seconds

    Order A arrives on channel A. It shows 1 in stock and is accepted.

  2. 5 minutes

    Order B arrives 5 minutes later. Channel B shows 1 unless a sync ran in between. A sync runs every 15 minutes, so the chance that none did, and B accepts, is 66.7%.

  3. After

    If B accepted, the next sync shows 0 in stock and 2 orders sold, and one must be cancelled or backordered.

Units in stock
1
Orders accepted
1 or 2
Oversold by
0 or 1
Chance both orders are accepted
66.7%
1 unit, 1 or 2 orders accepted: a 66.7% chance of 2.

What this design costs. Cheapest to build. Oversells whenever two orders land inside the lag.

The four designs at a glance

The four designs at a glance: the chance both orders are accepted and what each costs
DesignChance both orders are acceptedWhat it costs
Copy, synced on a schedule66.7%Cheapest to build. Oversells whenever two orders land inside the lag.
Copy, pushed on every sale100%Faster than a schedule, but a slow or failed push is another lag, and orders at the same moment still both pass.
Split the units between channels0%No oversell, but stock sits on the wrong channel and sales are lost.
One count, reserved at the source0%No oversell. Every order waits for the source, and a channel cannot sell if the source is down.

The second order arrives 5 minutes after the first and the sync lag is 15 minutes.

What you give up, and how it fails.

What you give up

What you get and what it costs
What you getWhat it costs
Each channel sells without waiting for the others, even when one is down.Two channels can sell the same unit inside the sync lag.
A copy is cheap to read and to cache.It is only as fresh as the last sync. A shorter lag narrows the window but never closes it.
Splitting the units between channels cannot oversell.Units sit on the wrong channel and sales are lost until someone rebalances.
One count, reserved at the source, cannot oversell.Every order waits for the source, and a channel cannot sell if the source is down.

How it fails

How the pattern fails, why, and the evidence
What happensWhyEvidence
A set overwrites a newer count.Frappe's Shopify connector sets Shopify's quantity to ERPNext's actual minus reserved stock as read at the time of the push. A sale that ERPNext has not imported yet is missing from that number, so the set puts the older count back. Medusa's Admin route for a stock level also takes the quantity to set. Shopify's docs say its set mutation takes absolute values, can compare the stored quantity with the one you last read, and is meant for a system that acts as the source of truth; otherwise they point to the adjust mutation.
Two lags stack.A Shopify order reaches ERPNext by webhook and a background job. The stock goes back to Shopify only on the next scheduled push, which is off by default: every 5, 10, 15, 30 or 60 minutes, default 60, checked on the scheduler's four-minute ticks. The window is both together.
ERPNext notices at delivery, not at the order.A submitted Sales Order only raises the Bin's reserved quantity. The negative-stock check runs when a voucher posts to the Stock Ledger, such as a Delivery Note, and only while Allow Negative Stock is off.
Add to cart says yes; checkout says no.Medusa checks stock when an item is added to a cart, which is a read. The reservation happens at checkout, under a lock, and re-checks inside its transaction. Stock gone in between fails the checkout.
The lock only holds on one instance.Medusa's docs say the Locking Module is what prevents overselling, and that its default in-memory provider is meant for a single instance. More than one instance needs another provider.
Syncing faster does not close the window.Orders at the same moment both read their own copy before either sale can travel, whatever the lag. Only one place that decrements the stock can refuse the second. The simulator shows it. Vogels calls the time until every observer sees an update the inconsistency window.
Follows from the designRead from:Vogels, eventual consistency

Each failure is Documented, with the maker's own page or code, or it Follows from the design. None is a result from a MithTech client project.

In ERPNext and Frappe, and Medusa.

Read from Frappe v15.123.0 and v16.52.0, ERPNext v15.122.0, Frappe's Ecommerce Integrations app (version-15, commit 3dc8eb81) and Medusa v2.21.2 on 14 October 2026, with the file and line. Other systems' names and settings were not checked here.

Where each part lives

The pattern in ERPNext and Frappe, and Medusa: feature, where it lives, and what it does
FeatureWhereWhat it does
Where the count lives,
Actual, reserved and projected quantity per item and warehouse. Projected is actual plus ordered, requested and planned, minus reserved. Submitting a Sales Order raises the reserved quantity and posts nothing to the Stock Ledger.ERPNext v15 · BinERPNext v15 · Sales Order
What refuses to go below zero
A Delivery Note, a Sales Invoice that updates stock or a Stock Entry fails with "N units of Item needed in Warehouse", unless Allow Negative Stock is on (Stock Settings, default off, or per item). Reserved stock is subtracted first.ERPNext v15 · negative stockERPNext v15 · Stock SettingsERPNext v15 · Delivery Note
Hold units for an order
Opt in under Stock Settings. Only against a Sales Order. Available to reserve is the balance minus open reservations, read with a row lock.ERPNext v15 · Stock Reservation
Push stock to a storeEcommerce Integrations › Shopify Setting
Sends actual minus reserved quantity as an absolute value, only for Bins changed since the last sync. Off by default. Every 5, 10, 15, 30 or 60 minutes (default 60), checked on the four-minute scheduler ticks.Ecommerce Integrations · stock pushEcommerce Integrations · changed BinsEcommerce Integrations · Shopify SettingEcommerce Integrations · sync gap
Take orders from a storeShopify webhooks
Creates and submits a Sales Order and skips an order already imported.Ecommerce Integrations · order importEcommerce Integrations · webhook topics
Medusa: one inventory for many channels linked to
A channel's stock operations are scoped to its linked stock locations. Channels of one store share the same stock levels: no copy and no lag between them.Medusa channels and stock
Medusa: check, then reserve,
Add to cart confirms with a read. Checkout reserves under a lock keyed by inventory item and checks again. allow_backorder is off by default.Medusa v2 · confirm inventoryMedusa v2 · reserve inventoryMedusa v2 · inventory moduleMedusa channels and stock
Medusa: set a stock level
Takes the quantity to set. The delta method exists inside Medusa's own fulfilment and return workflows.Medusa Admin · stock level

How to build it

  1. Decide where the count is decremented. One place, your ERP or your commerce platform's inventory, is the source; every channel asks it or follows it.

  2. If a channel must work offline or on a copy, hold back a few units: stop selling when the copy says a few remain, and size the margin to the orders that arrive inside one lag.

  3. Send changes, not whole counts: a delta, or the count with the value you last read so the receiver can refuse a stale one (Shopify calls it compareQuantity).

  4. Shorten the lag where you can, but plan for the window to stay open. Orders at the same moment need one decrement point.

  5. Count the shelves on a schedule and post the difference, a Stock Reconciliation in ERPNext. It repairs drift; it does not prevent it.

Questions about eventual consistency in stock