Inventory Sync Amazon Shopify NetSuite Odoo Multi-channel
Published · May 2026 Read time · 12 min Platforms · Amazon · Shopify · NetSuite · Odoo Region · Mexico · LATAM
Amazon FBA
Seller Central
QTY: 142
Shopify
Storefront
QTY: 138
Mercado Libre
Marketplace
QTY: 155
Molten Sync
Single Source
of Truth
QTY: 138 ✓
NetSuite
ERP
QTY: 161
Odoo
ERP
QTY: 147
SAME SKU · 5 SYSTEMS · 5 DIFFERENT COUNTS → MOLTEN SYNC RESOLVES TO ONE SOURCE OF TRUTH

You sell the same product on Amazon, Shopify, and Mercado Libre. Your ERP — whether it's NetSuite or Odoo — is supposed to be the source of truth. But log into each system and you'll see five different numbers for the same SKU. One says 138, another says 155, your ERP says 161. Which one do you trust? And more importantly — which one is Amazon about to delist you for?

This is one of the most common operational problems we solve at Molten Logistics. It's not a bug. It's not a coincidence. It's the predictable result of how each platform counts inventory — and the mismatched sync patterns between them. This guide explains exactly why it happens, platform by platform, and gives you a concrete fix strategy.

Who this is for: E-commerce operators, warehouse managers, and technical teams running inventory across two or more of these platforms: Amazon (FBA or FBM), Shopify, NetSuite, Odoo, Mercado Libre, eBay, or Walmart Marketplace.

WHY THE NUMBERS NEVER MATCH

Each platform has its own definition of "inventory on hand" — and they're genuinely different, not just synced poorly. Before fixing the problem, you need to understand what each number actually means.

Amazon FBA
142 units
Fulfillable inventory
Excludes: reserved, stranded, FC transfer
Shopify
138 units
Available to sell
Excludes: committed to open orders
NetSuite
161 units
Quantity on hand
Includes: all locations, committed, in-transit

All three numbers can be simultaneously correct — within each platform's own definition. The problem is that your team, your 3PL, and your customers are all seeing different numbers and acting on them differently.

CAUSE 01

Different inventory definitions per platform

Amazon's "fulfillable" count excludes units in FC transfer, stranded inventory, and reserved stock. Shopify's "available" count excludes committed (in open orders). NetSuite's "on hand" includes every unit in every location regardless of reservation status. They're measuring different things with the same word.

CAUSE 02

Asynchronous sync — orders outrun the update

A customer buys on Amazon at 2:04 PM. Your sync job runs every 15 minutes. At 2:05 PM your Shopify still shows the pre-sale quantity. During that 10-minute window, another customer buys the same unit on Shopify. You've oversold. The sync wasn't wrong — it was just slow.

CAUSE 03

FBA in-transit inventory counted (or not)

When you send a replenishment shipment to Amazon FBA, those units are "in transit" to the FC. Amazon's API returns them as unavailable. Your ERP may have already deducted them from warehouse stock. Now both systems show fewer units than you actually own — total inventory appears to vanish for days.

CAUSE 04

Multi-location confusion in the ERP

NetSuite and Odoo track inventory by location. If you have units at your warehouse, at a 3PL, and at Amazon FBA, the ERP total is the sum of all locations. But only the warehouse and 3PL units are actually available for non-FBA channels. Using the total ERP number to update Shopify leads to immediate overselling.

CAUSE 05

Returns not reconciled in real time

Amazon processes a return. The unit goes to "unfulfillable" — not back to fulfillable — until Amazon inspects it (which can take 10–45 days). Your ERP may credit the return immediately. Now the ERP shows more units than Amazon will ever sell. Meanwhile the unit may be damaged and never return to stock.

CAUSE 06

Bundle / kit explosion inconsistency

You sell a bundle of 3 units on Shopify. Each component is also sold individually on Amazon. Your ERP tracks components; Amazon and Shopify track finished bundles. Unless your sync layer explicitly handles kit explosion and implosion, each sale of the bundle on Shopify silently reduces component availability without updating Amazon.


PLATFORM-BY-PLATFORM BREAKDOWN

Amazon — the most complex inventory model

Amazon's inventory API returns multiple quantity types that must be understood before you can build a sync:

Amazon Quantity TypeWhat it meansInclude in sync?
fulfillableUnits Amazon can ship right now✓ Yes — this is your sellable stock
inbound_workingShipments you've created but not shipped✗ Not available yet
inbound_shippedShipments in transit to FC✗ Not available yet
inbound_receivingAt the FC, being checked in✗ Not available yet
reserved_customerordersSold but not yet shipped✗ Already committed
reserved_fc-transfersMoving between FCs✗ Temporarily unavailable
unfulfillableDamaged, expired, or pending inspection✗ Do not count
researchingLost or being investigated✗ Do not count
Common mistake: Using total (the sum of all types) to update your ERP or other channels. This will inflate your available count by hundreds of units and guarantee overselling. Always use fulfillable only for your sellable quantity.

Shopify — deceptively simple, with a catch

Shopify's inventory model is cleaner but has its own trap. Each SKU has an inventory_quantity (what's shown) and a incoming count (on the way). The inventory_quantity Shopify shows in-store is already net of committed units for open orders — but only if you have "continue selling when out of stock" disabled.

If that setting is enabled, Shopify will happily sell into negative inventory and never fire a webhook to warn you. You find out when you can't fulfill.

NetSuite — accurate but slow and location-aware

NetSuite is usually the most accurate ledger of inventory — when it's kept up to date. The key integration traps:

  • Location-level sync: Never push the company-total quantity to a single-location channel. Query by location (inventoryLocation) and push only the correct location's quantityAvailable.
  • Committed quantity: NetSuite's quantityOnHand includes committed stock. Use quantityAvailable (on hand minus committed) for channel updates.
  • Saved search latency: NetSuite's SuiteScript or REST API reads can have 5–15 minute lag on saved searches during peak hours. Build your sync to handle this gracefully.

Odoo — flexible but requires location discipline

Odoo's inventory tracks qty_on_hand, virtual_available (on hand + incoming − outgoing), and qty_available (immediately available). For channel sync, use qty_available filtered by the specific warehouse location you're selling from.

Python · Query Odoo available qty for a specific warehouse location
def get_odoo_available_qty(sku: str, warehouse_id: int) -> float:
    models, uid = odoo_connect()

    # Get the product first
    products = models.execute_kw(ODOO_DB, uid, ODOO_KEY,
        "product.product", "search_read",
        [[("default_code", "=", sku)]],
        {"fields": ["id", "name"], "limit": 1}
    )
    if not products:
        raise ValueError(f"SKU not found in Odoo: {sku}")

    product_id = products[0]["id"]

    # Read qty_available at the specific warehouse location
    # Use with_context to scope to the warehouse location
    quant = models.execute_kw(ODOO_DB, uid, ODOO_KEY,
        "stock.quant", "search_read",
        [[
            ("product_id", "=", product_id),
            ("location_id.usage", "=", "internal"),
            ("location_id.warehouse_id", "=", warehouse_id),
        ]],
        {"fields": ["quantity", "reserved_quantity", "location_id"]}
    )

    total_on_hand  = sum(q["quantity"] for q in quant)
    total_reserved = sum(q["reserved_quantity"] for q in quant)
    return max(0, total_on_hand - total_reserved)   # never push negative

HOW TO FIX IT — THE SYNC STRATEGY

The solution is a single source of truth with a unidirectional sync pattern. Your ERP (NetSuite or Odoo) is the master. Channels (Amazon, Shopify, Mercado Libre) are subscribers. Nothing updates the ERP except warehouse operations. Everything else reads from it.

1

Define your single source of truth — and enforce it

Pick one system to own inventory. For operations with an ERP, that's always your ERP. Your ERP is updated by warehouse receiving, sales orders, adjustments, and returns. No channel should ever write inventory back to the ERP — they should only read from it.

The only exception: Amazon FBA returns that need to be reconciled as adjustments, and this should go through a manual or semi-automated review step, not an automatic write-back.

No ERP yet? If you're running Shopify without an ERP, designate one location in Shopify as your master. All other channels subscribe to it. This is a temporary architecture — as volume grows, you'll need an ERP to manage multi-location correctly.
2

Establish your safety buffer per channel

Never push your full available quantity to any channel. Always reserve a safety buffer — a percentage or fixed unit count — that absorbs sync lag. The right buffer depends on your sales velocity and sync frequency:

Sync frequencyDaily sales velocityRecommended buffer
Every 15 min< 10 units/day2–3 units or 5%
Every 15 min10–50 units/day5–8 units or 8%
Every 15 min> 50 units/day10–15 units or 10%
Every 60 minAny velocityMinimum 15% — consider faster sync
Daily batchAny velocityNot recommended for live channels
3

Build event-driven updates alongside scheduled sync

A sync job that runs every 15 minutes is not enough for high-velocity SKUs. You need two complementary mechanisms:

  • Scheduled pull: Every 15 minutes, pull fresh quantities from your ERP and push to all channels. This is your baseline.
  • Event-driven push: When an order is placed on any channel, immediately decrement a running "committed" counter and push the updated available quantity to all other channels. This closes the 15-minute gap.
Python · Event-driven inventory update across channels
import threading
from dataclasses import dataclass

# In-memory committed counter — backed by Redis in production
committed_qty: dict[str, int] = {}
committed_lock = threading.Lock()

def on_order_placed(sku: str, qty_sold: int, source_channel: str):
    """
    Called immediately when any channel fires an order webhook.
    Decrements local committed counter and pushes to all other channels.
    """
    with committed_lock:
        committed_qty[sku] = committed_qty.get(sku, 0) + qty_sold

    # Get current ERP available qty
    erp_available = get_odoo_available_qty(sku, warehouse_id=1)

    # Subtract committed (sales not yet reflected in ERP)
    effective_qty = max(0, erp_available - committed_qty.get(sku, 0))

    # Apply safety buffer
    SAFETY_BUFFER = get_safety_buffer(sku)
    push_qty = max(0, effective_qty - SAFETY_BUFFER)

    # Push to all channels except the one that just sold
    channels = {"amazon", "shopify", "mercadolibre"} - {source_channel}
    for channel in channels:
        update_channel_inventory(channel=channel, sku=sku, qty=push_qty)

def update_channel_inventory(channel: str, sku: str, qty: int):
    if channel == "shopify":
        shopify_set_inventory(sku=sku, qty=qty)
    elif channel == "amazon":
        # Amazon FBA quantity is managed by Amazon — only update FBM
        if is_fbm_sku(sku):
            amazon_update_fbm_qty(sku=sku, qty=qty)
    elif channel == "mercadolibre":
        meli_update_stock(sku=sku, qty=qty)
4

Handle FBA inventory separately from FBM and other channels

This is the most misunderstood aspect of multi-channel sync. Amazon FBA inventory is physically at Amazon's fulfillment centers — you cannot add to it or subtract from it the way you can with your own warehouse. The rules:

  • FBA fulfillable qty is read-only from your perspective — Amazon manages it
  • FBA inventory should feed into your ERP as a separate location (e.g., "Amazon FBA — US East")
  • Never push your ERP's FBA location qty back to Amazon — you'll create a circular loop
  • Only FBM (Fulfilled by Merchant) listings on Amazon need your inventory push
  • FBA returns become unfulfillable — create a daily reconciliation job to identify units that have transitioned from unfulfillable back to fulfillable after Amazon's inspection
5

Build a daily reconciliation report

No real-time sync is perfect. You need a daily job that compares your ERP's authoritative count against every channel's reported quantity and flags deltas above your threshold. This is your early warning system — it catches drift before it becomes an oversell incident.

Python · Daily cross-channel inventory reconciliation
def daily_reconciliation_report() -> list[dict]:
    """
    Compares ERP qty against each channel for every active SKU.
    Flags SKUs where any channel diverges by more than tolerance %.
    """
    TOLERANCE_PCT = 0.05   # 5% variance is acceptable drift
    discrepancies = []

    active_skus = get_all_active_skus()   # from your ERP

    for sku in active_skus:
        erp_qty      = get_odoo_available_qty(sku, warehouse_id=1)
        shopify_qty  = shopify_get_inventory(sku)
        amazon_qty   = amazon_get_fulfillable(sku)   # FBM only
        meli_qty     = meli_get_stock(sku)

        checks = {
            "shopify":     shopify_qty,
            "amazon_fbm":  amazon_qty,
            "mercadolibre":meli_qty,
        }

        for channel, channel_qty in checks.items():
            if erp_qty == 0:
                continue
            variance = abs(erp_qty - channel_qty) / erp_qty
            if variance > TOLERANCE_PCT:
                discrepancies.append({
                    "sku":          sku,
                    "channel":      channel,
                    "erp_qty":      erp_qty,
                    "channel_qty":  channel_qty,
                    "variance_pct": round(variance * 100, 1),
                    "delta":        channel_qty - erp_qty,
                })

    # Sort by severity (largest absolute delta first)
    return sorted(discrepancies, key=lambda x: abs(x["delta"]), reverse=True)

SYNC PATTERNS THAT CREATE MORE PROBLEMS

The #1 mistake: Bidirectional sync between channels. If Amazon updates your ERP and your ERP updates Shopify and Shopify updates Amazon, you have a circular dependency that amplifies every small error into a large one. Inventory sync must be unidirectional: ERP → channels. Period.
MISTAKE 01

Bidirectional sync

Letting channels write inventory back to the ERP. A return on Amazon updates the ERP, the ERP updates Shopify, Shopify fires a webhook that updates the ERP again. You end up with phantom inventory that multiplies with every sync cycle.

MISTAKE 02

Using Amazon total qty instead of fulfillable

Pulling total from the Amazon inventory API and pushing it everywhere. This includes units in transit, reserved, and under investigation. Your actual sellable count is fulfillable only.

MISTAKE 03

Syncing all ERP locations as one number

Summing inventory across all NetSuite or Odoo locations and pushing the total to Shopify. Units at your closed warehouse, a bonded warehouse, or in-transit to FBA are not available to ship from your main DC.

MISTAKE 04

No buffer on fast-moving SKUs

Pushing your exact available quantity to every channel simultaneously. A flash sale or a viral moment on any one channel will oversell you across all others before the next sync cycle runs.

MISTAKE 05

Ignoring bundle component depletion

A bundle of 3 units sells on Shopify. The sync updates the bundle SKU but not the component SKUs. Amazon is still showing full availability for the components, which are now overstated by 3 units each bundle sale.

MISTAKE 06

Treating sync failures as silent non-events

A rate limit from Amazon's API causes a sync update to fail silently. The channel keeps its last-known quantity for hours. No alert fires. You discover the problem when customer service reports oversells the next morning.


INVENTORY SYNC HEALTH CHECKLIST

Run through this list for every SKU that sells on more than one channel. If you can't check every box, you have an active oversell risk.

🏗️ Architecture

  • Single system designated as master inventory — ERP if you have one, otherwise one Shopify location
  • Sync is strictly unidirectional: master → channels only. No channel writes back to master.
  • FBA inventory tracked as a separate location in the ERP — not included in sellable qty for non-FBA channels
  • Bundle / kit SKUs have explicit component depletion logic — every bundle sale reduces component availability across all channels
  • Multi-warehouse ERP: sync pushes per-location qty, not company total

⚡ Sync Configuration

  • Scheduled sync runs at least every 15 minutes for any SKU with > 5 units/day velocity
  • Event-driven update fires within 60 seconds of any order webhook from any channel
  • Safety buffer defined and enforced per SKU (never push 100% of available qty)
  • Amazon: using fulfillable quantity only — not total
  • NetSuite: using quantityAvailable — not quantityOnHand
  • Odoo: using qty_on_hand − reserved_quantity scoped to the selling warehouse
  • Shopify "continue selling when out of stock" is disabled for all SKUs where you can't honor oversells

🔔 Monitoring

  • Alert fires when any sync update fails — not just logged, actually alerted (Slack, PagerDuty, email)
  • Daily reconciliation report running and reviewed by ops team
  • Threshold for reconciliation alert defined (> 5% variance triggers review)
  • Amazon unfulfillable inventory reviewed weekly — units may be restorable after inspection
  • Rate limit handling implemented — sync retries with exponential backoff, does not silently drop failed updates

🔁 Returns & Adjustments

  • Amazon returns process defined: unfulfillable → inspection → fulfillable or write-off, with ERP adjustment at each stage
  • Shopify returns automatically trigger ERP inventory adjustment via webhook, not manual entry
  • Manual inventory adjustments in any channel are logged and the ERP is updated within 24 hours
  • Shrinkage, damage, and cycle count adjustments originate in the ERP and flow out to channels — not the reverse

INVENTORY DRIFT IS FIXABLE

Every discrepancy in this post has a technical solution. The reason most operations don't fix it isn't that the engineering is too hard — it's that there's always a fire burning somewhere else. The inventory count problem stays in the background, quietly causing oversells, Amazon suspensions, and customer refunds, until it becomes the fire.

A properly implemented unidirectional sync with event-driven updates, per-channel safety buffers, and a daily reconciliation report will eliminate 95% of the discrepancy problems described in this guide. The remaining 5% — FBA return reconciliation, bundle kitting edge cases, and rate limit resilience — are operational refinements you add as you stabilize the core.

🔥

Molten Logistics — Your trusted partner in Logistics & E-commerce

We've built inventory sync systems connecting Amazon, Shopify, Mercado Libre, eBay, NetSuite, and Odoo for operations across Mexico and LATAM. We've processed over 100,000 shipments with automated inventory management — and we know exactly where the edge cases live. Luis Alba and the team are ready to audit your current sync architecture and close the gaps.

Schedule a free consultation →
Quick recap: Each platform has a different inventory definition → use only the right quantity field per platform → designate one ERP as master → sync unidirectionally → apply safety buffers → add event-driven updates → run daily reconciliation → alert on every failure. That's the complete stack.
Molten Logistics