Storefront
Shopify
Marketplace
Amazon
LATAM Market
Mercado Libre
System of Record
ERP
via Molten Engine
Open Source ERP
Odoo 16 / 17
Enterprise ERP
NetSuite
SHOPIFY · AMAZON · MELI → ORDERS IN → ERP (SYSTEM OF RECORD) → INVENTORY · STOCK · ACCOUNTING OUT

The most expensive architectural mistake in multi-channel ecommerce is having no system of record — or having three of them. When Shopify thinks it owns inventory, Amazon thinks it owns inventory, and your ERP thinks it owns inventory, you don't have three sources of truth. You have three sources of conflict. Every order, every stock update, every price change becomes a negotiation between three systems that all believe they're right.

For ecommerce operations in Mexico, Colombia, and the US running Shopify as their DTC storefront, Amazon (US or Amazon.com.mx) as their marketplace, and either Odoo or NetSuite as their ERP, this question isn't academic. It determines whether your inventory is accurate, whether your accounting reconciles, whether your warehouse ships the right quantities, and whether your ops team spends their time growing the business or manually fixing discrepancies.

This post answers the question definitively: how to choose your system of record, what the rules are for each data domain, and how to architect the sync layer that enforces those rules across Shopify, Amazon, and your ERP.

GEO scope: This post is written for operations running Shopify (US or Mexico/Colombia storefront), Amazon.com and/or Amazon.com.mx, and either Odoo or NetSuite as their ERP — with a particular focus on US brands entering LATAM and LATAM brands with US expansion ambitions. Mercado Libre is treated as an additional channel where applicable.

What "System of Record" actually means

A system of record is the system whose value wins in any conflict. If Shopify says you have 10 units and your ERP says 8, the system of record's number is correct — the other one is stale and must be updated. There is no averaging, no manual review, no "let's check both." The SoR decides.

The challenge is that different data domains have different natural owners. Inventory is not the same as orders. Orders are not the same as product catalog. Product catalog is not the same as pricing. Your SoR architecture must define ownership at the domain level, not at the system level. Trying to make one system own everything is where most integrations go wrong.

Inventory

Owner: ERP (Odoo or NetSuite)

Your ERP tracks every physical movement of stock — receipts from suppliers, transfers between warehouses, adjustments, cycle count results. It's the only system connected to your warehouse or 3PL. Shopify and Amazon read from it; they never write to it except via the integration layer.

Orders

Owner: Channel (where the sale happened)

Shopify owns Shopify orders. Amazon owns Amazon orders. They push to the ERP for fulfillment. The ERP never creates an order back to the channel — it only confirms fulfillment. Orders are a one-way flow: channel → ERP.

Product Catalog

Owner: ERP (master SKU) + channels (presentation)

Your ERP owns the canonical SKU, product name, and cost. Shopify and Amazon own the presentation layer — titles, descriptions, images, bullet points. A product SKU change in the ERP propagates to channels. A Shopify title change does not propagate back to the ERP.

Pricing

Owner: ERP (cost + margin rules) → channel-specific price lists

Your ERP owns cost and target margin. The integration layer computes channel-specific prices by applying channel rules (Amazon fees, Shopify DTC margin, MELI commission in LATAM). No channel ever sets a price that propagates back to the ERP's cost or margin targets.

FBA / FULL Inventory

Owner: Amazon / MELI (for their FC stock) — read-only to ERP

Units physically at Amazon FBA or MELI FULL fulfillment centers are owned by those platforms until sold. Your ERP tracks them as a separate location (read-only) — it cannot instruct Amazon or MELI to move, adjust, or transfer this stock. These units are never included in Shopify's available inventory.

Accounting

Owner: ERP (always, no exceptions)

Revenue, cost of goods, accounts receivable, accounts payable — all owned by the ERP. Shopify Payments, Amazon Seller Central, and Mercado Pago each have their own financial summaries, but they are inputs to the ERP's accounting, not replacements for it. For LATAM operations, CFDI (Mexico), DIAN (Colombia), and Nota Fiscal (Brazil) are all generated from the ERP.


The three candidate architectures

Teams running Shopify, Amazon, and an ERP typically evolve through three architectural phases, usually in this order. Understanding why the first two fail is what makes the third one stick.

Shopify as SoR

✗ Fails at scale

Works when you only sell on Shopify. Breaks the moment Amazon FBA stock diverges from your warehouse stock, or when a purchase order receipt needs to be reflected across channels simultaneously. Shopify has no concept of multi-warehouse, no cost tracking, no supplier management.

Amazon Seller Central as SoR

✗ Never the right answer

Amazon's inventory model tracks FBA stock only — it has no visibility into your warehouse, your Shopify-fulfilled stock, or your MELI inventory. Using Amazon as SoR means your Shopify inventory is perpetually wrong and your accounting is a disaster. This architecture exists only accidentally.

Odoo vs NetSuite: choosing your ERP SoR for LATAM + US operations

DimensionOdoo 16 / 17NetSuite
LATAM localizationStrong — Mexico CFDI 4.0, Colombia DIAN, Brazil Nota Fiscal available as community/OCA modulesAvailable via SuiteApp — less mature for Mexico/Colombia than Odoo
Multi-currency (ARS volatility)Native multi-currency with daily rate updatesNative multi-currency with automated exchange rate feeds
Multi-company (MX + CO subsidiaries)Native multi-company in Odoo 16+Native subsidiary management — better for large orgs
API for integrationXML-RPC + REST (Odoo 17) — well-documented, community Python librariesREST + SuiteQL + SuiteScript — powerful but steeper learning curve
Total cost (SMB LATAM brand)$300–$800/mo — open source, self-hosted or Odoo.sh$2,000–$5,000+/mo — enterprise licensing
US cross-border opsFunctional but requires more configurationStrong — widely used by US brands with LATAM subsidiaries
Molten Logistics recommendationLATAM-first SMB/mid-market brands (<$20M revenue)US-first enterprise brands with LATAM expansion (>$20M revenue)

The sync rules that enforce your SoR

Deciding your ERP is the system of record is easy. Enforcing it across three channels, two fulfillment models (self-fulfilled and FBA/FULL), and multiple countries is where most teams struggle. These rules make it concrete.

1

Inventory flows outward from the ERP — never inward from channels

The ERP's qty_available (Odoo) or quantityAvailable (NetSuite) is the authority. Every 15 minutes, push the per-channel safe quantity to Shopify and Amazon. Every sale on any channel triggers an immediate event-driven push to all other channels. No channel write-back to ERP stock except through the integration layer's reconciliation process.

Python · ERP → all channels inventory push (Odoo + Shopify + Amazon)
SAFETY_BUFFER  = 5   # never push 100% of available
AMAZON_EXTRA   = 3   # FBM-specific extra buffer — FBA is read-only

def push_inventory_all_channels(sku: str, odoo_wh_id: int,
                                   shopify_variant_id: int, shopify_location_id: int,
                                   amazon_seller_sku: str, is_fbm: bool):
    """
    Reads authoritative qty from Odoo and pushes to all channels.
    FBA listings are NEVER pushed to — Amazon manages FBA qty.
    """
    models, uid = odoo_connect()

    # Step 1: Get Odoo available qty at selling warehouse (excludes FBA/FULL locations)
    quants = models.execute_kw(ODOO_DB, uid, ODOO_KEY,
        "stock.quant", "search_read",
        [[("product_id.default_code", "=", sku),
          ("location_id.usage", "=", "internal"),
          ("location_id.warehouse_id", "=", odoo_wh_id),
          ("location_id.x_is_fba_location", "=", False)]],
        {"fields": ["quantity", "reserved_quantity"]}
    )
    available = sum(q["quantity"] - q["reserved_quantity"] for q in quants)
    base_qty  = max(0, int(available) - SAFETY_BUFFER)

    # Step 2: Push to Shopify
    requests.post(
        f"https://{SHOPIFY_SHOP}.myshopify.com/admin/api/2024-04/inventory_levels/set.json",
        headers=shopify_headers(),
        json={"location_id": shopify_location_id,
              "inventory_item_id": shopify_variant_id,
              "available": base_qty}
    )

    # Step 3: Push to Amazon FBM only (never push to FBA listings)
    if is_fbm:
        fbm_qty = max(0, base_qty - AMAZON_EXTRA)
        sp_api.patch(
            f"/listings/2021-08-01/items/{AMAZON_SELLER_ID}/{amazon_seller_sku}",
            params={"marketplaceIds": AMAZON_MARKETPLACE_ID},
            body={"productType": "PRODUCT", "patches": [{
                "op": "replace",
                "path": "/attributes/fulfillment_availability",
                "value": [{"fulfillment_channel_code": "DEFAULT", "quantity": fbm_qty}]
            }]}
        )
2

Orders flow inward to the ERP from all channels

Every sale on Shopify, Amazon, or Mercado Libre creates a record in the ERP. The ERP owns fulfillment. The ERP confirms shipment. The channel receives the tracking confirmation. This is a strict one-way flow for orders — and it is the most important rule to enforce because violations create duplicate shipments, under-shipments, and accounting gaps.

  • Shopify orders → Odoo sale.order via webhook orders/paid
  • Amazon orders → Odoo sale.order via SP-API Orders endpoint polling + webhook
  • MELI orders → Odoo sale.order via Notifications API orders_v2
  • ERP ships → ERP creates stock.picking → validates → pushes tracking to originating channel
  • No channel creates its own fulfillment record in the ERP — the ERP's warehouse module is the only source of fulfillment truth
Mexico + Colombia B2B note: For B2B buyers on any channel, the ERP must capture the buyer's RFC (Mexico) or NIT (Colombia) at order creation. This is required for CFDI 4.0 (Mexico) and DIAN e-invoicing (Colombia). No channel stores this natively — your order ingestion pipeline must prompt for or look up the tax ID and attach it to the res.partner before the sale order is confirmed.
3

FBA and FULL inventory are tracked as read-only ERP locations

Amazon FBA units and MELI FULL units are not in your warehouse. Your ERP must track them as separate stock locations — not as warehouse stock. This is non-negotiable for accurate financial reporting and correct channel inventory.

Location typeOdoo location nameSync directionShopify includes?MELI non-FULL includes?
Your warehouse (MX)WH/Stock — MéxicoERP → all channels✓ Yes✓ Yes
Your warehouse (CO)WH/Stock — ColombiaERP → CO channels✓ Yes (CO location)✓ Yes (MCO)
Amazon FBA — USAmazon FBA US (virtual)Amazon → ERP (read)✗ Never✗ Never
Amazon FBA — MXAmazon FBA México (virtual)Amazon → ERP (read)✗ Never✗ Never
MELI FULL — CDMXMELI FULL CDMX (virtual)MELI → ERP (read)✗ Never✗ Never (separate FULL listing)
In-transit to FBAWH/Output → FBA transitERP internal✗ Never✗ Never
4

Product catalog changes flow from ERP outward — never reverse

Your ERP owns the canonical SKU and product data. When a product is created, renamed, or discontinued in the ERP, that change propagates to channels. When a Shopify product description is updated, it stays on Shopify — it does not update the ERP product name.

The exception: product attributes specific to each channel (Amazon bullet points, MELI item attributes, Shopify SEO description) are maintained in the channel directly. Never try to sync these back to the ERP — they don't belong there and the mapping will fail.

Python · ERP product change → push SKU update to Shopify + Amazon
def on_erp_product_updated(odoo_product_id: int, changed_fields: list[str]):
    """
    Called when product.template is written in Odoo.
    Only propagates fields that channels care about.
    """
    models, uid = odoo_connect()
    product = models.execute_kw(ODOO_DB, uid, ODOO_KEY,
        "product.template", "read", [odoo_product_id],
        {"fields": ["name", "default_code", "list_price", "active",
                    "x_meli_item_id", "x_shopify_product_id", "x_amazon_seller_sku"]}
    )

    # Fields that propagate to Shopify
    shopify_propagate = {"list_price", "active"}   # NOT name — Shopify owns its own title
    if shopify_propagate & set(changed_fields):
        shopify_update_product(
            product_id=product["x_shopify_product_id"],
            price=product["list_price"],
            active=product["active"],
        )

    # Fields that propagate to Amazon FBM
    amazon_propagate = {"list_price", "active"}
    if amazon_propagate & set(changed_fields) and product["x_amazon_seller_sku"]:
        amazon_update_listing(
            seller_sku=product["x_amazon_seller_sku"],
            price=compute_amazon_price(product["list_price"]),
        )

    # Fields that propagate to MELI
    meli_propagate = {"list_price"}
    if meli_propagate & set(changed_fields) and product["x_meli_item_id"]:
        meli_update_price(
            item_id=product["x_meli_item_id"],
            price=compute_meli_price(product["list_price"]),
        )
5

Accounting and financial reporting live exclusively in the ERP

Amazon Seller Central has revenue reports. Shopify has a P&L. Mercado Pago has transaction summaries. None of these replace your ERP's accounting module. They are data sources that feed into it.

For Mexico, every invoice generated for a Shopify or MELI sale must originate from the ERP's CFDI 4.0 module — not from Shopify's invoice generator or from MELI's internal billing. For Colombia, DIAN e-invoicing is the same. For Brazil, Nota Fiscal. The ERP is the legal accounting system of record for LATAM operations — there is no alternative.

  • Shopify Payments daily payout → reconcile against Odoo account.payment journal entries
  • Amazon disbursements (2x/month) → reconcile against ERP AR for Amazon channel
  • Mercado Pago balance → reconcile against ERP AR for MELI channel
  • Mexico: CFDI 4.0 generated from ERP per order — required for all B2C and B2B sales
  • Colombia: DIAN electronic invoice generated from ERP — mandatory for registered businesses
6

Daily reconciliation confirms your SoR is actually enforced

Declaring the ERP as your system of record and actually enforcing it are two different things. The only way to know the rules are holding is a daily reconciliation job that compares ERP data against every channel and flags variances. This is your audit trail and your early warning system.

Python · Daily SoR health check across all channels
def daily_sor_reconciliation() -> dict:
    """
    Runs daily. Compares ERP state against all channels.
    Returns dict of discrepancies for ops dashboard.
    """
    report = {"inventory_drift": [], "order_gaps": [], "price_mismatches": []}
    active_skus = get_all_active_skus_from_erp()

    for sku in active_skus:
        erp_qty      = get_odoo_available(sku)
        shopify_qty  = shopify_get_inventory(sku)
        amazon_qty   = amazon_get_fbm_qty(sku)    # FBM only

        # Inventory drift check (allow SAFETY_BUFFER variance)
        for channel, ch_qty in {"shopify": shopify_qty, "amazon_fbm": amazon_qty}.items():
            expected = max(0, erp_qty - SAFETY_BUFFER)
            drift = abs(expected - ch_qty)
            if drift > 3:   # more than 3 units drift → investigate
                report["inventory_drift"].append({
                    "sku": sku, "channel": channel,
                    "erp_qty": erp_qty, "channel_qty": ch_qty, "drift": drift
                })

        # Order gap check: ERP orders today vs channel order count today
        erp_orders     = get_erp_orders_today_for_sku(sku)
        shopify_orders = shopify_orders_today(sku)
        amazon_orders  = amazon_orders_today(sku)

        if len(erp_orders) < len(shopify_orders) + len(amazon_orders):
            report["order_gaps"].append({
                "sku": sku,
                "erp_count": len(erp_orders),
                "channel_total": len(shopify_orders) + len(amazon_orders),
            })

    return report

SoR rules specific to LATAM operations

The ERP-as-SoR architecture has additional requirements when your operations span Mexico, Colombia, Argentina, or Brazil. These are not optional enhancements — they're legal and operational requirements that don't exist for pure US operations.

CountryERP SoR requirementWhy it mattersFails without ERP?
🇲🇽 MexicoCFDI 4.0 e-invoice from ERP for every saleLegal requirement for all commercial transactions — SAT complianceYes — Shopify/MELI cannot generate CFDI
🇨🇴 ColombiaDIAN electronic invoice from ERPMandatory for registered companies — all B2B and B2C above thresholdYes — channels don't generate DIAN documents
🇧🇷 BrazilNota Fiscal from ERP for every outbound shipmentRequired for shipping — carrier won't pick up without NFYes — Shopify cannot generate Nota Fiscal
🇦🇷 ArgentinaARS price stored as USD equivalent in ERP; converted at push timeARS volatility makes fixed ARS prices economically wrong within 30 daysPartially — prices drift without ERP as master
🇲🇽🇨🇴 MX + COSeparate ERP operating units per countryDifferent tax rates, currencies, fiscal positions, chart of accountsYes — commingled accounting is legally invalid
🇺🇸→🇲🇽 US cross-borderHS codes on all products in ERP — required for MELI CBT customs clearanceMissing HS codes cause customs holds and delayed LATAM deliveriesYes — channels don't manage HS codes
US brands entering LATAM: The most common mistake US brands make when expanding to Mexico via Shopify Mexico + Amazon.com.mx + MELI is treating the LATAM ERP setup as "the same as US but with different taxes." Mexico's CFDI 4.0 requires specific XML fields (UUID, CURP for individuals, RFC, usage code) that your US ERP setup will not have configured. Budget 4–8 weeks for Mexico fiscal localization in Odoo before your first LATAM sale.

SoR implementation checklist

🏗️ Architecture Decisions

  • ERP declared as system of record — documented and agreed by engineering, ops, and finance teams
  • Data domain ownership table published: who owns inventory, orders, catalog, pricing, accounting
  • No channel has direct write-back to ERP inventory outside of the integration layer
  • FBA and MELI FULL tracked as separate read-only ERP locations — never mixed with warehouse stock
  • For LATAM: separate Odoo operating unit (or NetSuite subsidiary) per country with legal entity

📦 Inventory Enforcement

  • Scheduled inventory push: ERP → Shopify + Amazon FBM every 15 minutes
  • Event-driven push fires on every order webhook from any channel
  • Safety buffer (5 units base + channel-specific extra) applied before every channel push
  • Shopify inventory never includes Amazon FBA or MELI FULL stock
  • Amazon FBA inventory synced to ERP FBA location daily — read only, no push back to Amazon

🛒 Order Flow

  • All channel orders create ERP sale.order records within 2 minutes of placement
  • Idempotency enforced: client_order_ref checked before any ERP order creation
  • ERP is the only system that creates stock.picking — no channel creates its own fulfillment
  • Fulfillment confirmation pushed from ERP back to originating channel on stock.picking done
  • Mexico B2B orders: RFC captured at order creation and stored on res.partner
  • Colombia B2B orders: NIT captured at order creation and stored on res.partner

💰 Accounting & LATAM Compliance

  • Mexico: CFDI 4.0 module configured in Odoo — every sale generates a valid XML fiscal document
  • Colombia: DIAN e-invoicing module active — every sale generates electronic invoice
  • Brazil: Nota Fiscal module configured — every outbound shipment generates NF before carrier pickup
  • Shopify Payments reconciled against Odoo bank statement daily
  • Amazon disbursements reconciled against Odoo AR for Amazon channel twice monthly
  • Mercado Pago balance reconciled against Odoo AR for MELI channel weekly

🔍 Monitoring & Reconciliation

  • Daily SoR reconciliation job: ERP qty vs every channel qty — drift > 3 units triggers alert
  • Daily order gap check: channel order count vs ERP sale.order count — gap triggers immediate investigation
  • Weekly price audit: ERP list price × channel rule = expected channel price — mismatch flagged
  • Monthly FBA/FULL reconciliation: ERP FBA/FULL location vs Amazon/MELI reported stock
  • Runbook documented: how to force-resync any data domain from ERP on demand

Your ERP is ready to be the boss

The system of record decision is an architecture decision, not a tool decision. Odoo and NetSuite are both capable of being the SoR for a multi-channel operation running Shopify, Amazon, and Mercado Libre. What determines success is not which ERP you choose — it's whether you enforce the ownership rules consistently across every data domain, every channel, and every country.

For US brands entering Mexico and Colombia, the fiscal localization layer is what most teams underestimate. CFDI 4.0 and DIAN e-invoicing are not add-ons — they're core infrastructure for your LATAM operation. Getting them right in your ERP before your first LATAM sale is far cheaper than retrofitting them after 10,000 transactions are already in the system with missing fiscal data.

🔥

Built this kind of architecture — and run it in production

Luis Alba and the Molten team have set up ERP-as-SoR architectures for Shopify + Amazon + Mercado Libre operations across Mexico, Colombia, and the US. From Odoo CFDI 4.0 setup to NetSuite multi-subsidiary configuration to the integration layer that enforces every SoR rule in production.

See if you qualify →
Quick recap: ERP owns inventory, accounting, and product master. Channels own orders and presentation. FBA/FULL stock is read-only in the ERP. Inventory flows outward from ERP only. Orders flow inward to ERP only. Accounting and fiscal documents (CFDI/DIAN/NF) always originate from the ERP. Daily reconciliation confirms the rules are holding. That's the complete system of record architecture.