by InlinexDev

Syncing Products Between Shopify and Shopee: A Technical Deep Dive

How we built a multi-channel sync system that keeps product listings, inventory, and orders in sync between Shopify and Shopee.

ShopeeShopifymulti-channelinventory syncNode.js

The Multi-Channel Challenge

Selling on both Shopify and Shopee is common in Southeast Asia. Shopify handles your branded store while Shopee gives you marketplace exposure. The problem? Keeping product data, inventory levels, and orders synchronized across both platforms is a nightmare without automation.

Inlinex Shopee Sync solves this with a bidirectional sync engine that runs on GitHub Actions and Railway.

Architecture

The sync system has three main components:

  1. Product Sync — creates and updates listings across platforms
  2. Inventory Sync — keeps stock levels accurate in real-time
  3. Order Sync — pulls Shopee orders into a central dashboard

Why GitHub Actions?

Scheduled syncs don't need a server running 24/7. GitHub Actions cron jobs handle periodic syncs (every 15 minutes for inventory, hourly for products). A lightweight Express server on Railway handles webhooks for real-time order notifications.

Product Sync: Mapping Between Platforms

Shopify and Shopee have fundamentally different data models. A Shopify product with variants maps to a Shopee listing with models:

function mapShopifyToShopee(shopifyProduct) {
  return {
    item_name: shopifyProduct.title,
    description: stripHtml(shopifyProduct.body_html),
    category_id: getCategoryMapping(shopifyProduct.product_type),
    price: parseFloat(shopifyProduct.variants[0].price),
    stock: shopifyProduct.variants.reduce((sum, v) => sum + v.inventory_quantity, 0),
    models: shopifyProduct.variants.map(variant => ({
      model_name: variant.title,
      model_sku: variant.sku,
      price: parseFloat(variant.price),
      stock: variant.inventory_quantity
    })),
    images: shopifyProduct.images.map(img => ({
      url: img.src
    }))
  };
}

Category Mapping

Shopee has a rigid category tree. We maintain a mapping table:

const CATEGORY_MAP = {
  'Inline Skates': 100636,
  'Protective Gear': 100637,
  'Wheels & Bearings': 100638,
  'Accessories': 100639
};

Image Handling

Shopee requires images to be uploaded to their CDN before creating a listing. You can't just pass a URL:

async function uploadImageToShopee(imageUrl) {
  const response = await shopeeApi.post('/api/v2/media_space/upload_image', {
    image_url: imageUrl
  });
  return response.data.response.image_info.image_url_list[0].image_url;
}

Inventory Sync: Preventing Overselling

The most critical sync is inventory. If a customer buys the last unit on Shopify, Shopee needs to know immediately. Our approach:

  1. Shopify webhooks trigger on inventory level changes
  2. Shopee stock update is called within seconds
  3. Conflict resolution — if both platforms process a sale simultaneously, the lower stock count wins
async function syncInventory(sku, shopifyQty) {
  const shopeeItem = await findShopeeBySku(sku);
  if (!shopeeItem) return;

  // Only update if there's a discrepancy
  if (shopeeItem.stock !== shopifyQty) {
    await shopeeApi.post('/api/v2/product/update_stock', {
      item_id: shopeeItem.item_id,
      stock_list: [{
        model_id: shopeeItem.model_id,
        seller_stock: [{ stock: shopifyQty }]
      }]
    });
    
    console.log(`Synced ${sku}: ${shopeeItem.stock} -> ${shopifyQty}`);
  }
}

Order Sync: Centralized Management

Shopee orders are pulled into the system and stored in PostgreSQL. This gives the merchant a single dashboard for all orders:

async function pullShopeeOrders() {
  const response = await shopeeApi.get('/api/v2/order/get_order_list', {
    params: {
      time_range_field: 'create_time',
      time_from: lastSyncTimestamp,
      time_to: Math.floor(Date.now() / 1000),
      page_size: 100,
      order_status: 'READY_TO_SHIP'
    }
  });

  for (const order of response.data.response.order_list) {
    await upsertOrder(order);
    // Deduct inventory on Shopify side
    await adjustShopifyInventory(order.item_list);
  }
}

Error Handling & Monitoring

Sync failures are inevitable — APIs go down, rate limits are hit, data is malformed. Our resilience strategy:

  • Exponential backoff on API failures
  • Dead letter queue for failed syncs that need manual review
  • Daily reconciliation job that compares all stock levels and fixes discrepancies
  • Alerting via webhook to a Slack channel when errors exceed thresholds

Lessons Learned

  1. Shopee API rate limits are strict — batch operations where possible
  2. Image URLs expire — cache Shopee CDN URLs, don't re-upload every sync
  3. Price sync is dangerous — always require manual approval for price changes
  4. Testing is hard — Shopee's sandbox is limited, so build comprehensive mocks

Conclusion

Multi-channel selling is essential for e-commerce growth in Southeast Asia, but only if the operational overhead is automated away. A well-built sync system turns two platforms into one unified operation.

Related Project

Shopify-Shopee Multi-Channel Sync

Automated product, inventory, and order sync between Shopify and Shopee Singapore, enabling multi-channel selling with real-time stock updates.