Mercado Libre
MLM · MCO · MLA
100M+ buyers LATAM
sync both
Molten Engine
Single Source of Truth
Inventory · Orders · Prices
sync both
Shopify
MX · CO · US
Direct-to-consumer
MELI + SHOPIFY → MOLTEN SINGLE SOURCE OF TRUTH → ZERO DRIFT · ZERO OVERSELL · ONE ERP

Running Mercado Libre and Shopify simultaneously is the standard growth pattern for ecommerce brands in Latin America. MELI gives you the marketplace volume — Mexico, Colombia, Argentina, Brazil. Shopify gives you the branded storefront, direct margins, and customer data. Together, they should multiply your revenue. In practice, they multiply your operational complexity.

The sync challenges between these two platforms are not theoretical. They're oversold items on Black Friday because a Shopify sale didn't update MELI fast enough. They're a price war with yourself because MELI listings show last week's promotion while Shopify shows today's price. They're duplicate orders in your ERP from the same customer who placed on both channels during a Hot Sale spike. They're returns that exist in MELI but have no corresponding movement in Shopify or your warehouse system.

At Molten Logistics, we've built and maintained multi-channel sync for brands operating across Mexico, Colombia, Argentina, and Brazil — and for US brands selling into LATAM through both channels simultaneously. This post documents the real challenges and the architecture decisions that solve them.

GEO scope: Every challenge in this post has country-specific callouts for Mexico (MLM), Colombia (MCO), Argentina (MLA), and Brazil (MLB). US brands using Shopify as their DTC storefront while entering LATAM via MELI Cross-Border Trade will find the cross-border section especially relevant.

Seven sync challenges that cost you money

Each of these challenges is a real operational failure mode. Not hypothetical — these are the problems Molten Logistics is called in to fix after they've already cost the business oversells, chargebacks, or customer complaints.

CHALLENGE 01

The oversell window

Between a sale on Channel A and the sync update reaching Channel B, there's a window where both channels show the same available unit. Any sale that lands in this window creates an oversell. The window size depends on sync frequency: 15-minute batch sync = 15-minute oversell window on every SKU.

Shipment cancellations · MELI reputation penalty · Shopify chargeback
CHALLENGE 02

Inventory drift under load

During high-volume events — Hot Sale (MX), Cyber Monday (CO/AR), Buen Fin (MX) — order velocity exceeds sync velocity. Inventory drift accumulates: MELI shows 12 units, Shopify shows 15, warehouse has 10. The drift compounds with every sync lag during peak periods.

Mass overselling during highest-revenue periods · worst possible timing
CHALLENGE 03

Price desynchronization

A promotion runs on Shopify (30% off for 48 hours). The MELI price isn't updated. Buyers see different prices on the same product on the same day. Worse: a marketplace tool or browser extension notices the Shopify price and screenshots it — now you're being tagged in social media for price inconsistency in Mexico City.

Brand damage · marketplace policy risk · customer support volume spike
CHALLENGE 04

Duplicate order records in the ERP

A customer buys on both MELI and Shopify with different accounts but the same shipping address. Two sale orders, one warehouse location, one unit. Without cross-channel dedup at the ERP level, both orders get fulfilled — one will fail and need cancellation and a refund.

Fulfilled order cancellations · double shipping cost · customer confusion
CHALLENGE 05

Return reconciliation failures

A return is approved on MELI. The unit comes back to your warehouse. Shopify has no idea — its inventory doesn't increase. Your ERP gets the return but doesn't know whether to restock or write off. The same unit may already be re-sold on Shopify while physically in transit back to you from a MELI return in Colombia.

Phantom inventory · unsatisfied Shopify orders · accounting discrepancies
CHALLENGE 06

SKU mapping divergence

Over time, MELI item IDs, Shopify variant IDs, and ERP internal references drift apart. A product gets discontinued on MELI but stays active in Shopify. A bundle is created in Shopify that doesn't exist in MELI. A component SKU changes in the ERP but the mapping tables aren't updated. Now the sync silently fails for affected SKUs.

Silent sync failures · stale inventory on discontinued SKUs · hidden stockouts
CHALLENGE 07

MELI FULL vs Shopify warehouse conflict

You enroll some SKUs in MELI Fullfilment (FULL) — MELI holds the stock at their centers in Mexico City or Bogotá. Shopify doesn't know these units exist at MELI's FC. If you naively push total ERP stock to Shopify (including the MELI FULL units), Shopify oversells stock that MELI has already locked into their FC and will fulfill independently.

MELI FULL fulfillment + Shopify fulfillment for the same unit · double shipment

The oversell window — how it actually happens

The oversell window is the most common failure mode in multi-channel LATAM operations and the hardest to eliminate completely. Understanding the timeline helps you design defenses that actually work.

TimeEventMELI showsShopify showsWarehouse reality
T+0:00Sync job runs — 10 units available101010
T+0:45Customer A buys 1 unit on MELI9 (updating)10 ← stale9
T+1:20Customer B buys 1 unit on Shopify — same unit9 ← stale9 (updating)8 — OVERSOLD
T+14:00Next sync job catches up888 — damage done

The fix has two components: a safety buffer that absorbs the lag window, and an event-driven decrement that fires immediately on any sale across either channel. Neither alone is sufficient at high velocity. Together, they reduce the oversell window to near-zero.

Python · Event-driven cross-channel inventory decrement
import threading
from dataclasses import dataclass, field
from typing import Literal

# In-process committed counter — back with Redis in production
_committed: dict[str, int] = {}
_lock = threading.Lock()

SAFETY_BUFFER = 5    # base buffer — never pushed
MELI_EXTRA    = 2    # extra for MELI's slower ack loop

def on_sale(sku: str, qty_sold: int, source: Literal["meli", "shopify"]):
    """
    Called immediately on any order webhook from either channel.
    Decrements committed counter and pushes updated qty to the OTHER channel.
    """
    with _lock:
        _committed[sku] = _committed.get(sku, 0) + qty_sold

    # Pull fresh available qty from Odoo (authoritative source)
    odoo_available = get_odoo_available(sku)

    # Net available = Odoo stock minus all uncommitted sales across channels
    net_available = max(0, odoo_available - _committed.get(sku, 0))

    # Push to the OTHER channel — the one that just sold already decremented itself
    if source == "meli":
        # Sale on MELI → update Shopify immediately
        shopify_qty = max(0, net_available - SAFETY_BUFFER)
        shopify_set_inventory(sku=sku, qty=shopify_qty)
    else:
        # Sale on Shopify → update MELI immediately
        meli_qty = max(0, net_available - SAFETY_BUFFER - MELI_EXTRA)
        meli_update_stock(sku=sku, qty=meli_qty)

def scheduled_full_sync(sku: str):
    """
    Runs every 15 min — reconciles committed counter against ERP reality.
    Clears drift from any sales that bypassed the event-driven path.
    """
    odoo_available = get_odoo_available(sku)
    with _lock:
        _committed[sku] = 0   # reset — ERP is now the single source of truth

    shopify_qty = max(0, odoo_available - SAFETY_BUFFER)
    meli_qty    = max(0, odoo_available - SAFETY_BUFFER - MELI_EXTRA)

    shopify_set_inventory(sku=sku, qty=shopify_qty)
    meli_update_stock(sku=sku, qty=meli_qty)
Mexico Hot Sale / Colombia Cyber Lunes: During LATAM's peak sale events, order velocity can spike 10–20× on both channels simultaneously. Your committed counter must be backed by Redis (not in-process memory) so multiple worker instances share the same decrement state. An in-memory counter on a single worker running in parallel with a second worker instance will double-count decrements and hold back 2× the committed stock.

Price sync — harder than it looks

Prices look like a simple sync problem — update both channels when your price changes. In LATAM multi-channel operations, there are four reasons it's not.

1

Channel pricing strategy is intentionally different

Most LATAM brands price MELI listings 10–20% higher than their Shopify DTC price to cover MELI's commission (typically 13–17% in Mexico and Colombia). Syncing the same price to both channels means you're either subsidizing MELI sales or overpricing your DTC store. You need a per-channel price rule engine, not a simple price copy.

  • MELI Mexico commission: 13–17% depending on category
  • MELI Colombia commission: 13.5–16.5% depending on category
  • Formula used by most Molten clients: MELI price = Shopify price × (1 + commission%) + fixed_fee_if_any
  • Promotions on MELI (descuentos) can be applied without changing the base price — use MELI's promotions API instead of directly editing the price
2

Promotional timing windows create dangerous desync

A Shopify promotion running for 48 hours needs to be reflected on MELI within the same window — but MELI price updates have a propagation delay of 5–30 minutes across their CDN before buyers see the new price. If you start a Shopify flash sale at 10:00 AM and push the MELI update at the same time, MELI buyers see the old price until 10:30 AM, then panic-buy at the new price until 10:00 AM the next day when your MELI price reverts but Shopify price already reverted at midnight.

Python · Coordinated price push to both channels with timestamp alignment
from datetime import datetime, timezone
import schedule, time

def start_promotion(sku: str, shopify_promo_price: float, meli_promo_price: float,
                     start_at: datetime, end_at: datetime):
    """
    Schedules coordinated price change on both channels.
    Pushes to MELI 30 min early to account for CDN propagation delay.
    """
    meli_start = start_at.replace(minute=start_at.minute - 30)   # 30min early for MELI CDN
    meli_end   = end_at.replace(minute=end_at.minute + 5)         # 5min late to avoid early revert

    # Schedule MELI price push (early)
    schedule.at(meli_start).do(meli_update_price, sku=sku, price=meli_promo_price)
    schedule.at(meli_end).do(meli_revert_price, sku=sku)

    # Schedule Shopify price push (on time)
    schedule.at(start_at).do(shopify_update_price, sku=sku, price=shopify_promo_price)
    schedule.at(end_at).do(shopify_revert_price, sku=sku)
3

Argentina ARS volatility makes any fixed price policy useless

In Argentina (MLA), the ARS peso can lose significant value within weeks. A price set in ARS today may be economically wrong by next month — not because your strategy changed, but because the currency moved. Your price sync must account for this:

  • Store your MLA prices as a USD equivalent in your ERP
  • Convert to ARS using Banco Nación official rate on every sync cycle
  • Round to MELI-valid price points (MELI Argentina has minimum price increments)
  • Never store ARS prices as static values in your product master — they'll be wrong within 30 days
  • Alert when ARS conversion changes your effective margin by more than 5%
4

MELI catalog mode blocks your price update in Argentina and Brazil

For catalog-listed products in MLA (Argentina) and MLB (Brazil), MELI controls the displayed price range based on competitor offers. You can push a price, but if it's outside MELI's acceptable range for that catalog product, MELI will silently reject or override it. Your sync must read back the effective price after pushing to detect this.

Python · Push price to MELI and verify effective price was accepted
def push_and_verify_meli_price(item_id: str, target_price: float, site_id: str) -> bool:
    """
    Pushes price to MELI and reads back to confirm acceptance.
    Returns False if MELI overrode the price (catalog mode rejection).
    """
    meli_put(f"/items/{item_id}", {"price": target_price}, site_id=site_id)

    # Wait for propagation then read back
    time.sleep(3)
    item = meli_get(f"/items/{item_id}", site_id=site_id)
    effective_price = item.get("price")

    variance = abs(effective_price - target_price) / target_price
    if variance > 0.01:   # more than 1% off — MELI overrode our price
        log_warning(f"MELI price override on {item_id} [{site_id}]: "
                    f"pushed {target_price}, effective {effective_price}")
        return False
    return True

SKU mapping — the silent failure

SKU mapping divergence is the most insidious multi-channel sync problem because it fails silently. An update fires, gets a 200 OK response, and updates the wrong product — or updates nothing because the mapping no longer exists. You won't notice until a customer complains or an audit surfaces the discrepancy.

The full SKU identifier picture

SystemProduct identifierVariant identifierSync key to maintain
Your ERP (Odoo)product.template.idproduct.product.default_code (SKU)Master — all others reference this
Mercado Libreitem_id (e.g. MLM123456)variation_id (integer)x_meli_item_id + x_meli_variation_id on Odoo product
Shopifyproduct_idvariant_id + sku fieldShopify sku field must match Odoo default_code
Warehouse / 3PLWMS item codeWMS location + lotMust also match Odoo default_code
The most common mapping failure in LATAM: A product is retired from MELI (paused or deleted) but remains active in Shopify. Your sync continues pushing Shopify price and inventory updates to the dead MELI item ID — getting silent 404s. Meanwhile your MELI item ID table in the ERP still shows the product as "active". This creates phantom inventory on a channel that no longer sells it.
Python · Daily SKU mapping health check — MELI + Shopify vs ERP
def daily_sku_mapping_audit() -> list[dict]:
    """
    Compares SKU mapping tables across MELI, Shopify, and Odoo.
    Returns list of discrepancies for ops team review.
    """
    models, uid = odoo_connect()
    issues = []

    # Get all Odoo products that have MELI mapping
    odoo_products = models.execute_kw(ODOO_DB, uid, ODOO_KEY,
        "product.template", "search_read",
        [[("x_meli_item_id", "!=", False), ("active", "=", True)]],
        {"fields": ["id", "name", "default_code", "x_meli_item_id", "x_meli_site_id"]}
    )

    for prod in odoo_products:
        meli_id  = prod["x_meli_item_id"]
        site_id  = prod["x_meli_site_id"]

        # 1. Check MELI item still exists and is active
        try:
            item = meli_get(f"/items/{meli_id}", site_id=site_id)
            if item.get("status") != "active":
                issues.append({
                    "sku":     prod["default_code"],
                    "issue":   "MELI item not active",
                    "detail":  f"Status: {item.get('status')} on {site_id}",
                })
        except Exception as e:
            issues.append({
                "sku":     prod["default_code"],
                "issue":   "MELI item not found",
                "detail":  f"item_id {meli_id} returned error: {e}",
            })

        # 2. Check Shopify variant exists with matching SKU
        shopify_variants = shopify_find_variant_by_sku(prod["default_code"])
        if not shopify_variants:
            issues.append({
                "sku":     prod["default_code"],
                "issue":   "Shopify variant not found",
                "detail":  f"No Shopify variant with sku={prod['default_code']}",
            })

    return sorted(issues, key=lambda x: x["issue"])

MELI FULL vs Shopify — the inventory split

Mercado Libre Fullfilment (FULL) is MELI's FBA equivalent — you ship stock to MELI's fulfillment centers (Mexico City, Monterrey, Bogotá, Buenos Aires, São Paulo) and MELI handles picking, packing, and last-mile delivery. It's a massive competitive advantage on MELI: FULL listings get the "Envío gratis" green badge and rank higher in search.

The multi-channel sync problem: units at MELI's FC are physically unavailable to fulfill Shopify orders. If your Shopify inventory includes FULL units, you will create Shopify orders for stock you cannot ship.

The FULL double-fulfillment scenario: You have 50 units of SKU-MX-001. 30 are at MELI's FULL FC in CDMX. 20 are at your warehouse. Your Shopify inventory incorrectly shows 50 (total). A Shopify flash sale sells 25 units. You try to fulfill 25 from your warehouse — but you only have 20. 5 orders get cancelled. Your customer satisfaction score drops. Your Shopify abandoned cart rate spikes for the next two weeks.
Python · Correct inventory push to Shopify — excluding MELI FULL units
def get_shopify_safe_qty(sku: str, odoo_wh_id: int, meli_full_location_id: int) -> int:
    """
    Returns the quantity that is safe to show on Shopify:
    - On-hand at YOUR warehouse (not MELI FULL FC)
    - Minus reserved for open orders
    - Minus safety buffer
    Never includes stock at MELI FULL locations.
    """
    models, uid = odoo_connect()

    # Warehouse stock (excludes MELI FULL location)
    warehouse_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", "!=", meli_full_location_id),   # exclude FULL
        ]],
        {"fields": ["quantity", "reserved_quantity"]}
    )
    warehouse_available = sum(
        q["quantity"] - q["reserved_quantity"] for q in warehouse_quants
    )

    # MELI FULL stock (read-only — used for MELI sync only, NEVER for Shopify)
    full_quants = models.execute_kw(ODOO_DB, uid, ODOO_KEY,
        "stock.quant", "search_read",
        [[("product_id.default_code", "=", sku),
          ("location_id", "=", meli_full_location_id)]],
        {"fields": ["quantity"]}
    )
    full_qty = sum(q["quantity"] for q in full_quants)

    # Log the split for ops visibility
    log_inventory_split(sku=sku, warehouse=warehouse_available, meli_full=full_qty)

    return max(0, int(warehouse_available) - SAFETY_BUFFER)
Colombia and Argentina FULL expansion: MELI is actively expanding FULL coverage in Bogotá (MCO) and Greater Buenos Aires (MLA). If you're currently managing FULL only for Mexico, your architecture must be designed to handle FULL per site — not as a single boolean flag on the SKU. A product can be FULL in Mexico but self-fulfilled in Colombia, with completely different inventory flows for each.

Returns — the unreconciled layer

Returns are where multi-channel sync breaks down most visibly in LATAM operations. The challenge is that a return on MELI, a return on Shopify, and a physical return to your warehouse are three separate events that your ERP must stitch together — and they rarely arrive in the same order or at the same time.

Return eventWhere it originatesERP action requiredInventory impactLATAM note
MELI return request approved MELI claims API Create reverse stock.picking (draft) None yet — unit still with buyer Legal window: 30d MX, 5d CO, 10d AR, 7d BR
MELI return unit in transit back MELI shipments webhook Confirm reverse picking is in transit None yet — unit still in transit Shipping label generated by MELI
Unit received at warehouse Warehouse / WMS scan Validate reverse picking + inspect +1 if restockable, write-off if damaged Brazil: Nota Fiscal de devolução required
MELI refund processed MELI payments webhook Create credit note in Odoo against original invoice None — financial only Refund in Mercado Pago — reconcile against MP statement
Shopify refund created Shopify refunds/create webhook Create credit note + restock if applicable +1 if restock_line = true in payload Shopify refund may restock automatically if configured
The reconciliation gap: A unit returned on MELI takes 7–21 days to physically arrive back at your warehouse in Mexico. During that time, Odoo may show the unit as "in return transit." If another sale comes in on Shopify for the last unit of that SKU, you have to make a call: hold the Shopify order until the return arrives, or cancel the Shopify order and disappoint the customer. Your returns architecture must surface these pending-return conflicts to your ops team in real time, not after the fact.

Multi-channel sync health checklist

⚡ Oversell Prevention

  • Event-driven inventory decrement fires on every order webhook from both MELI and Shopify
  • Committed counter backed by Redis — not in-process memory (supports multiple workers)
  • Safety buffer (min 5 units) applied before pushing to either channel
  • Additional MELI buffer (2–3 units) for slower MELI sync acknowledgment cycle
  • Scheduled full sync every 15 min resets committed counter to ERP reality
  • Peak event plan: buffer increased and sync frequency doubled for Hot Sale / Buen Fin / Cyber Monday

💰 Price Sync

  • Per-channel price rules: MELI price = Shopify price × (1 + commission%) per category per country
  • Promotion timing: MELI price pushed 30 min early to account for CDN propagation delay
  • Argentina ARS: prices stored as USD equivalent in ERP, converted to ARS at push time using Banco Nación rate
  • MLA/MLB catalog mode: price pushed and verified — read-back check confirms MELI accepted the price
  • Price change alert: team notified when effective MELI price differs from pushed price by more than 1%

🗂️ SKU Mapping Integrity

  • Daily audit job: every Odoo product with x_meli_item_id is verified active on MELI
  • Daily audit job: every Odoo product's default_code exists as sku on a Shopify variant
  • Stale MELI item IDs flagged and paused — sync does not fire to dead item IDs
  • Bundles and kits explicitly handled — component depletion updates both channels
  • Product retirement process: deactivate MELI listing → deactivate Shopify product → mark ERP product inactive — in that order

📦 MELI FULL Isolation

  • Dedicated Odoo location per MELI FULL FC (CDMX, MTY, BOG, GBA, SP) — not shared with warehouse stock
  • Shopify inventory push explicitly excludes all MELI FULL locations
  • x_meli_is_full flag set per product.product per site — not one flag for all sites
  • MELI FULL stock is read-only for Shopify — no cross-fulfillment between channels
  • FULL replenishment transfers tracked in Odoo as in-transit to MELI FULL location

🔄 Returns Reconciliation

  • MELI return request creates reverse stock.picking in Odoo immediately
  • Return transit conflicts surfaced to ops team — alerts when pending return overlaps low stock situation
  • Restockable vs write-off decision logged at warehouse receipt — not assumed
  • Brazil returns: Nota Fiscal de devolução generated before restocking
  • MELI refund reconciled against Mercado Pago statement in Odoo accounting
  • Shopify refund restock behavior configured explicitly — automatic restock disabled for damaged returns
  • Weekly returns reconciliation: MELI return count vs Odoo reverse pickings vs physical receipts

Sync problems are fixable — with the right architecture

Every sync challenge in this post has a technical solution. The oversell window closes with event-driven decrements and safety buffers. Price desync disappears with per-channel price rules and coordinated timing. SKU drift is caught with daily audits. The FULL conflict resolves with location-level inventory isolation. Returns reconcile when you build the layer instead of skipping it.

The brands that get this right in LATAM are the ones that treat multi-channel sync as infrastructure — not as a background task or a phase 2 feature. In Mexico and Colombia, where both MELI and Shopify audiences are growing simultaneously, getting the sync wrong costs you during your highest-revenue periods: Hot Sale, Buen Fin, Cyber Lunes. Getting it right means those events are your best days, not your worst support queue.

🔥

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

Luis Alba and the Molten team have built and maintained multi-channel sync for brands operating across Mexico, Colombia, Argentina, and Brazil — and for US brands entering LATAM through Mercado Libre and Shopify simultaneously. We've run these systems through Hot Sale, Buen Fin, Cyber Monday, and everything in between. We know where the sync breaks and exactly how to fix it.

Schedule a free consultation →
Quick recap: Close the oversell window with event-driven decrements + Redis committed counter + safety buffer. Sync prices with per-channel commission rules + coordinated promotion timing. Audit SKU mappings daily. Isolate MELI FULL stock per site — never push to Shopify. Build the returns reconciliation layer at launch. Monitor all of it continuously with country-specific alerts. That's the complete stack.