Ecommerce-to-ERP Integration: Order & Inventory Sync for Shopify, Magento, and BigCommerce

Type: ERP Integration Systems: Shopify, Adobe Commerce, BigCommerce, NetSuite, SAP S/4HANA, D365 BC Confidence: 0.85 Sources: 7 Verified: 2026-03-07 Freshness: 2026-03-07

TL;DR

System Profile

This integration playbook covers connecting the three dominant ecommerce platforms -- Shopify, Adobe Commerce (Magento 2), and BigCommerce -- to major ERP systems for automated order processing and inventory synchronization. The primary data flows are: orders from ecommerce to ERP (including customer data, line items, payment status, and shipping addresses), and inventory/fulfillment data from ERP back to ecommerce (stock levels, tracking numbers, shipment status). [src2, src6]

The playbook focuses on cloud-to-cloud integrations using native APIs and iPaaS middleware. On-premise Magento installations follow the same API patterns but require network/firewall configuration not covered here. For direct CRM-to-ERP integration (e.g., Salesforce opportunity to NetSuite sales order without an ecommerce frontend), see the Order-to-Cash Integration card instead. [src3]

SystemRoleAPI SurfaceDirection
ShopifyEcommerce storefront -- order captureREST Admin, GraphQL Admin, WebhooksOutbound (orders) / Inbound (inventory)
Adobe Commerce (Magento 2)Ecommerce storefront -- order captureREST, GraphQL, Message QueuesOutbound (orders) / Inbound (inventory)
BigCommerceEcommerce storefront -- order captureREST V2/V3, WebhooksOutbound (orders) / Inbound (inventory)
Oracle NetSuiteERP -- financial master, fulfillmentSuiteTalk SOAP, REST API, RESTletsInbound (orders) / Outbound (inventory, fulfillment)
SAP S/4HANAERP -- financial master, fulfillmentOData v4, IDocInbound (orders) / Outbound (inventory, fulfillment)
D365 Business CentralERP -- financial master, fulfillmentOData v4, REST API v2.0Inbound (orders) / Outbound (inventory, fulfillment)
iPaaS MiddlewareOrchestrator -- data transformationCeligo, Boomi, MuleSoft, WorkatoBidirectional

API Surfaces & Capabilities

API SurfacePlatformProtocolBest ForMax Records/RequestRate LimitReal-time?Bulk?
GraphQL Admin APIShopifyHTTPS/JSONOrders, products, inventory (recommended)250 items per input100-1,000 pts/s by planYesVia Bulk Operations
REST Admin APIShopifyHTTPS/JSONLegacy, simple CRUD250 per page2 req/s (40 bucket)YesNo
WebhooksShopifyHTTPS/JSONEvent-driven order capture1 event per callN/A (push)YesNo
REST APIMagento 2HTTPS/JSONOrders, products, inventory300 per pageDisabled by defaultYesVia async endpoints
Async Bulk APIMagento 2HTTPS/JSONHigh-volume inventory updates5,000 per requestConfigurable via RedisNoYes
REST V3 APIBigCommerceHTTPS/JSONOrders, products, catalog250 per page150-450 req/30sYesNo
WebhooksBigCommerceHTTPS/JSONEvent-driven order capture1 event per callN/A (push)YesNo
SuiteTalk SOAPNetSuiteHTTPS/XMLOrder creation, inventory reads1,000 per search5 concurrent (default)YesVia CSV import
OData v4SAP S/4HANAHTTPS/JSONSales orders, inventory5,000 per pageThrottled/fair-useYesVia $batch
REST API v2.0D365 BCHTTPS/JSONSales orders, items, inventory20,000 per pageThrottled/fair-useYesVia $batch

Rate Limits & Quotas

Per-Request Limits

Limit TypeValuePlatformNotes
Max items per GraphQL mutation250ShopifyUse bulkOperationRunMutation for larger batches
Max query cost per request1,000 pointsShopify GraphQLSingle query cannot exceed this regardless of plan
Max REST response page size250 recordsShopify RESTUse page_info cursor pagination
Max records per search1,000NetSuite SuiteTalkUse searchMoreWithId for pagination
Max $top per OData request5,000SAP S/4HANAServer-driven paging for larger sets
Max request body100 MBMagento 2 AsyncFor bulk async operations
Max webhook response time19 secondsShopifyExceed = timeout + retry queue

Rolling / Daily Limits

Limit TypeValueWindowPlatform Differences
Shopify GraphQL points100-1,000/sPer second (leaky bucket)Standard: 100, Advanced: 200, Plus: 1,000, Enterprise: 2,000
Shopify REST requests40 bucket (2/s refill)RollingShared across all apps on store
BigCommerce API requests150-450 / 30s30-second windowStandard/Plus: 150, Pro: 450, Enterprise: custom
BigCommerce webhooks60 / minPer minuteAcross all webhook consumers
NetSuite concurrent requests5-10Per accountDefault 5; SuiteCloud Plus: 10+
NetSuite governance units1,000 (scheduled) / 10,000 (map-reduce)Per script executionSuiteScript 2.x governance

Authentication

PlatformFlowUse WhenToken LifetimeRefresh?Notes
ShopifyOAuth 2.0Public/custom appsPermanent (offline)NoOffline access tokens don't expire
ShopifyCustom App TokenPrivate integrationsPermanentNoGenerated in Shopify Admin
Magento 2OAuth 1.0aThird-party integrationsPermanent (until revoked)No4-step handshake required
Magento 2Bearer TokenAdmin/customer API4 hours (admin)NoPOST /rest/V1/integration/admin/token
BigCommerceOAuth + API TokenAll integrationsPermanentNoStore-level, scoped permissions
NetSuiteToken-Based Auth (TBA)Server-to-serverPermanent (until revoked)NoRecommended over credentials
SAP S/4HANAOAuth 2.0 Client CredentialsServer-to-serverConfigurable (default 12h)YesRequires x-csrf-token for writes
D365 BCOAuth 2.0 (Azure AD)Server-to-server1 hourYesAzure App Registration required

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START -- Ecommerce-to-ERP integration
|-- What data flow?
|   |-- Orders (ecommerce to ERP)
|   |   |-- Volume < 100 orders/day?
|   |   |   |-- YES: Webhook-driven (near-real-time, simplest)
|   |   |   +-- NO:
|   |   |-- Volume 100-1,000 orders/day?
|   |   |   |-- YES: Webhook + queue (RabbitMQ/SQS) then ERP batch insert
|   |   |   +-- NO:
|   |   |-- Volume > 1,000 orders/day?
|   |   |   |-- YES: Webhook + queue + iPaaS batch processing (every 5-15min)
|   |   |   +-- Flash sale / > 10K/day? iPaaS with auto-scaling + ERP bulk import
|   |   +-- Need immediate ERP confirmation?
|   |       |-- YES: Synchronous API call (watch rate limits!)
|   |       +-- NO: Async webhook + queue + batch (recommended)
|   |-- Inventory (ERP to ecommerce)
|   |   |-- SKU count < 1,000? Scheduled poll every 5-15min
|   |   |-- SKU count < 50,000? Delta sync (changed-since) every 5-15min
|   |   +-- Need < 1min latency? ERP-side CDC/webhook push
|   |-- Fulfillment/Tracking (ERP to ecommerce)
|   |   +-- Always push-based: ERP triggers update via middleware
|   +-- Product/Catalog (ERP to ecommerce)
|       +-- Scheduled batch sync (hourly/daily)
|-- Which middleware?
|   |-- < 100 orders/day: Direct API (no middleware needed)
|   |-- 100-1,000/day: Celigo, Workato, or native connector
|   |-- > 1,000/day: MuleSoft, Boomi, or Celigo scaling tier
|   +-- Multi-channel: iPaaS required
+-- Error handling?
    |-- Zero-loss: Idempotent writes + DLQ + reconciliation job
    +-- Best-effort: Retry with exponential backoff (max 5 retries)

Quick Reference

StepSource SystemActionTarget SystemData ObjectsFailure Handling
1Shopify/Magento/BCOrder placed -- webhook firesiPaaS QueueOrder header, line items, customer, shippingWebhook retries (Shopify: 19x/48h)
2iPaaSTransform + validate order dataERPSales Order / Sales InvoiceRetry 3x with backoff, then DLQ
3iPaaSCreate/match customer recordERPCustomer / Business PartnerMatch on email; create if not found
4iPaaSMap SKUs to ERP item IDsERPItem / Material / ProductFail order if SKU not found (alert)
5ERPAllocate inventory, create pick/packWMS / 3PLFulfillment recordManual review queue
6ERPShip order, generate trackingiPaaSShipment + tracking numberRetry push to ecommerce
7iPaaSUpdate order with trackingShopify/Magento/BCFulfillment / Shipment recordRetry 3x, then alert
8ERPAdjust inventory quantitiesiPaaSInventory levels per locationDelta sync on schedule
9iPaaSPush inventory updatesShopify/Magento/BCInventory quantities per SKURetry, then full reconciliation
10ERPInvoice created, payment matchedAccountingInvoice + paymentManual reconciliation

Step-by-Step Integration Guide

1. Configure Webhook Listeners for Order Capture

Register webhooks on your ecommerce platform to receive order events. This eliminates polling and ensures near-real-time order flow. [src1, src4]

Shopify (GraphQL Admin API):

mutation {
  webhookSubscriptionCreate(
    topic: ORDERS_CREATE
    webhookSubscription: {
      callbackUrl: "https://your-middleware.com/webhooks/shopify/orders"
      format: JSON
    }
  ) {
    webhookSubscription { id }
    userErrors { field message }
  }
}

BigCommerce (REST V3):

curl -X POST https://api.bigcommerce.com/stores/{store_hash}/v3/hooks \
  -H "X-Auth-Token: {api_token}" \
  -H "Content-Type: application/json" \
  -d '{"scope": "store/order/created", "destination": "https://your-middleware.com/webhooks/bigcommerce/orders", "is_active": true}'

Verify: Shopify Admin > Settings > Notifications > Webhooks shows new subscription; BigCommerce: GET /v3/hooks returns active hook

2. Receive and Queue Order Webhooks

Never process orders synchronously in the webhook handler. Acknowledge immediately, then queue for async processing. [src3, src7]

# Python (Flask) -- webhook receiver with queue
import hmac, hashlib, json, base64
from flask import Flask, request, jsonify
import boto3

app = Flask(__name__)
sqs = boto3.client('sqs')
QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/123456789/ecommerce-orders"

@app.route('/webhooks/shopify/orders', methods=['POST'])
def shopify_order_webhook():
    # Verify HMAC signature (critical for security)
    hmac_header = request.headers.get('X-Shopify-Hmac-Sha256', '')
    digest = hmac.new(b'YOUR_WEBHOOK_SECRET', request.data, hashlib.sha256).digest()
    computed = base64.b64encode(digest).decode()
    if not hmac.compare_digest(computed, hmac_header):
        return jsonify({"error": "Invalid signature"}), 401
    # Queue immediately, respond fast (< 5s)
    order = json.loads(request.data)
    sqs.send_message(QueueUrl=QUEUE_URL, MessageBody=json.dumps({
        "platform": "shopify", "order_id": order["id"], "payload": order
    }), MessageGroupId="orders")
    return jsonify({"status": "queued"}), 200

Verify: Send a test order → check queue depth increases by 1

3. Transform and Map Order Data to ERP Schema

Map ecommerce order fields to ERP-specific sales order format. This is where most integrations break. [src3, src7]

# Python -- Shopify order to NetSuite Sales Order mapping
def map_shopify_to_netsuite(shopify_order):
    customer = {
        "email": shopify_order["email"],
        "firstName": shopify_order["customer"]["first_name"],
        "lastName": shopify_order["customer"]["last_name"],
        "externalId": f"SHOP-{shopify_order['customer']['id']}"
    }
    items = []
    for item in shopify_order["line_items"]:
        items.append({
            "item": {"externalId": item["sku"]},
            "quantity": item["quantity"],
            "rate": float(item["price"]),
        })
    sales_order = {
        "externalId": f"SHOP-{shopify_order['order_number']}",
        "entity": {"externalId": f"SHOP-{shopify_order['customer']['id']}"},
        "tranDate": shopify_order["created_at"][:10],
        "itemList": {"item": items},
    }
    return customer, sales_order

Verify: Run mapping on a sample order → validate all required ERP fields populated, no nulls in mandatory fields

4. Create Sales Order in ERP with Idempotency

Use external IDs to make order creation idempotent -- retrying the same order must not create duplicates. [src3]

# Python -- NetSuite REST API order creation with idempotency
def create_netsuite_order(sales_order, auth_headers):
    response = requests.post(NETSUITE_URL, json=sales_order, headers={
        **auth_headers, "Content-Type": "application/json",
        "Prefer": "return=representation"
    })
    if response.status_code == 204:
        return {"status": "created", "id": response.headers.get("Location")}
    elif response.status_code == 409:
        return {"status": "already_exists"}  # Idempotent success
    elif response.status_code == 429:
        retry_after = int(response.headers.get("Retry-After", 5))
        raise RateLimitError(f"Retry after {retry_after}s")
    else:
        raise IntegrationError(f"Error {response.status_code}: {response.text}")

Verify: GET /services/rest/record/v1/salesOrder?q=externalId IS "SHOP-1001" returns the created order

5. Sync Inventory from ERP to Ecommerce (Delta Sync)

Pull inventory changes from ERP since last sync, then push updates to ecommerce platform. Always ERP to ecommerce direction. [src2, src3]

# Python -- NetSuite to Shopify inventory sync (delta)
def sync_inventory_netsuite_to_shopify(last_sync_time):
    suiteql = f"""SELECT item.itemId AS sku, il.quantityAvailable AS qty
        FROM inventoryBalance il JOIN item ON il.item = item.id
        WHERE item.lastModifiedDate > '{last_sync_time}'"""
    changed_items = netsuite_query(suiteql)
    for item in changed_items:
        shopify_graphql("inventorySetQuantities", {
            "name": "available", "reason": "correction",
            "quantities": [{"inventoryItemId": sku_to_id(item["sku"]),
                "locationId": LOCATION_ID, "quantity": int(item["qty"])}]
        })

Verify: Change quantity in NetSuite → run sync → Shopify Admin shows updated stock

6. Push Fulfillment and Tracking Back to Ecommerce

When the ERP ships an order, push tracking information back to the ecommerce platform. [src3]

// Node.js -- Push NetSuite fulfillment to Shopify
async function pushFulfillmentToShopify(orderId, trackingNumber, carrier) {
  const shopifyOrderId = orderId.replace('SHOP-', '');
  const foResponse = await axios.get(
    `https://${SHOP}.myshopify.com/admin/api/2025-01/orders/${shopifyOrderId}/fulfillment_orders.json`,
    { headers: { 'X-Shopify-Access-Token': ACCESS_TOKEN } }
  );
  const fulfillmentOrderId = foResponse.data.fulfillment_orders[0].id;
  await axios.post(
    `https://${SHOP}.myshopify.com/admin/api/2025-01/fulfillments.json`,
    { fulfillment: {
        line_items_by_fulfillment_order: [{ fulfillment_order_id: fulfillmentOrderId }],
        tracking_info: { number: trackingNumber, company: carrier },
        notify_customer: true
    }},
    { headers: { 'X-Shopify-Access-Token': ACCESS_TOKEN } }
  );
}

Verify: GET /admin/api/2025-01/orders/{id}/fulfillments.json shows status: success with tracking_number

Code Examples

Python: Shopify Bulk Inventory Update via GraphQL Bulk Operations

# Input:  Dict of SKU to quantity mappings from ERP export
# Output: Shopify inventory levels updated for all changed SKUs

import requests, json, time

SHOP = "your-store.myshopify.com"
TOKEN = "shpat_xxxxx"
HEADERS = {"X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json"}
API_URL = f"https://{SHOP}/admin/api/2025-01/graphql.json"

def bulk_inventory_update(sku_qty_map, location_id):
    # Build JSONL for bulk mutation
    jsonl_lines = [json.dumps({"input": {"name": "available", "reason": "correction",
        "quantities": [{"inventoryItemId": sku_to_id(sku),
            "locationId": f"gid://shopify/Location/{location_id}",
            "quantity": qty}]}}) for sku, qty in sku_qty_map.items()]
    # Stage upload, then run bulk mutation
    # ... (see full code in .md file)

JavaScript/Node.js: BigCommerce Order Fetch with Rate Limit Handling

// Input:  BigCommerce store credentials + date range
// Output: Array of order objects for ERP transformation

async function fetchOrdersWithRateLimit(sinceDate) {
  let allOrders = [], page = 1, hasMore = true;
  while (hasMore) {
    try {
      const response = await axios.get(`${BC_API}/orders`, {
        headers: HEADERS,
        params: { min_date_created: sinceDate, limit: 250, page }
      });
      allOrders = allOrders.concat(response.data.data);
      const remaining = parseInt(response.headers['x-rate-limit-requests-left'] || '100');
      if (remaining < 10) {
        const resetMs = parseInt(response.headers['x-rate-limit-time-reset-ms'] || '5000');
        await new Promise(r => setTimeout(r, resetMs));
      }
      hasMore = response.data.data.length === 250;
      page++;
    } catch (err) {
      if (err.response?.status === 429) {
        const resetMs = parseInt(err.response.headers['x-rate-limit-time-reset-ms'] || '15000');
        await new Promise(r => setTimeout(r, resetMs));
      } else throw err;
    }
  }
  return allOrders;
}

cURL: Quick API Tests for Each Platform

# Shopify: Test auth + fetch recent orders
curl -s "https://YOUR-STORE.myshopify.com/admin/api/2025-01/orders.json?limit=5&status=any" \
  -H "X-Shopify-Access-Token: shpat_XXXXX" | jq '.orders | length'

# BigCommerce: Test auth + fetch orders
curl -s "https://api.bigcommerce.com/stores/STORE_HASH/v3/orders?limit=5" \
  -H "X-Auth-Token: YOUR_TOKEN" | jq '.data | length'

# Magento 2: Get bearer token + fetch orders
TOKEN=$(curl -s -X POST "https://your-magento.com/rest/V1/integration/admin/token" \
  -H "Content-Type: application/json" -d '{"username":"admin","password":"pass"}' | tr -d '"')
curl -s "https://your-magento.com/rest/V1/orders?searchCriteria[pageSize]=5" \
  -H "Authorization: Bearer $TOKEN" | jq '.items | length'

Data Mapping

Field Mapping Reference

Source Field (Ecommerce)NetSuite TargetSAP S/4HANA TargetD365 BC TargetTypeGotcha
order.idsalesOrder.externalIdSalesOrder.PurchaseOrderByCustomersalesOrder.externalDocumentNumberStringPrefix with platform code (SHOP-, BC-, MAG-)
order.order_numbersalesOrder.otherRefNumSalesOrder.CustomerPurchaseOrderNumbersalesOrder.numberStringShopify order_number != id
order.created_atsalesOrder.tranDateSalesOrder.SalesOrderDatesalesOrder.orderDateDateConvert timezone: Shopify=UTC, NetSuite=user pref
order.total_pricesalesOrder.totalSalesOrder.TotalNetAmountsalesOrder.totalAmountIncludingTaxDecimalVerify 2 vs 4 decimal places
order.currencysalesOrder.currency.externalIdSalesOrder.TransactionCurrencysalesOrder.currencyCodeStringNetSuite uses internal IDs, not ISO codes by default
line_items[].skuitem.externalIdSalesOrderItem.MaterialsalesLine.itemNoStringTHE #1 FAILURE POINT -- must match exactly
line_items[].quantityitem.quantitySalesOrderItem.RequestedQuantitysalesLine.quantityIntegerCheck UOM mapping (each vs case)
line_items[].priceitem.rateSalesOrderItem.NetAmountsalesLine.unitPriceDecimalTax-inclusive (Shopify EU) vs tax-exclusive (ERPs)
customer.emailcustomer.emailBusinessPartner.EmailAddresscustomer.emailStringPrimary match key for deduplication
shipping_address.zipshippingAddress.zipShipToParty.PostalCodeshipToAddress.postalCodeStringInternational formats vary (UK: SW1A 1AA)

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodePlatformMeaningCauseResolution
429AllRate limit exceededToo many API calls in windowExponential backoff: wait 2^n * 1s, max 5 retries. Read Retry-After header
422ShopifyUnprocessable EntityInvalid data (missing required field)Check response body for field-level errors; fix data mapping
401AllUnauthorizedExpired or invalid tokenRe-authenticate; check app isn't uninstalled
409NetSuiteConflict / DuplicateexternalId already existsIdempotent -- this is success for retry scenarios
400MagentoBad RequestInvalid search criteria or missing queueVerify searchCriteria syntax; check RabbitMQ running
RCRD_DSNT_EXISTNetSuiteRecord not foundReferenced item/customer doesn't existVerify SKU mapping; create missing master data first

Failure Points in Production

Anti-Patterns

Wrong: Polling ecommerce platform for new orders every 60 seconds

# BAD -- wastes API quota, adds latency, hits rate limits at scale
while True:
    orders = shopify.get("/orders.json?status=any&created_at_min=" + last_check)
    for order in orders:
        create_erp_order(order)
    time.sleep(60)

Correct: Use webhooks with queue-based processing

# GOOD -- event-driven, near-real-time, respects rate limits
@app.route('/webhooks/shopify/orders', methods=['POST'])
def handle_order_webhook():
    verify_hmac(request)
    queue.enqueue(request.data)  # Queue immediately
    return '', 200  # Respond fast
# Queue consumer processes orders at sustainable rate

Wrong: Bidirectional inventory sync without conflict resolution

# BAD -- creates infinite loops and ghost stock
def sync_inventory():
    shopify_qty = get_shopify_inventory(sku)
    netsuite_qty = get_netsuite_inventory(sku)
    if shopify_qty != netsuite_qty:
        update_netsuite(sku, shopify_qty)  # Wrong! Shopify is not source of truth
        update_shopify(sku, netsuite_qty)  # Overwrites what you just pushed

Correct: Unidirectional inventory sync (ERP as source of truth)

# GOOD -- ERP is single source of truth for inventory
def sync_inventory():
    changed_items = query_netsuite_changed_since(last_sync_time)
    for item in changed_items:
        update_shopify_inventory(sku=item["sku"], quantity=item["quantityAvailable"])
    save_last_sync_time(datetime.utcnow())

Wrong: Hardcoded SKU mapping in code

# BAD -- breaks every time a new product is added
SKU_MAP = {"BLK-SHIRT-L": "10042", "WHT-SHIRT-M": "10043"}  # 2000 more...

Correct: Dynamic SKU lookup with ERP external IDs

# GOOD -- SKU matching via ERP external ID field
def get_erp_item_id(sku):
    result = netsuite_search("item", {"externalId": sku})
    if not result:
        raise SKUNotFoundError(f"SKU '{sku}' not found in ERP")
    return result["internalId"]

Common Pitfalls

Diagnostic Commands

# Shopify: Check API usage / remaining limits
curl -sI "https://YOUR-STORE.myshopify.com/admin/api/2025-01/shop.json" \
  -H "X-Shopify-Access-Token: TOKEN" | grep -i "x-shopify"
# Look for: X-Shopify-Shop-Api-Call-Limit: 2/40

# BigCommerce: Check rate limit headers
curl -sI "https://api.bigcommerce.com/stores/HASH/v3/catalog/products?limit=1" \
  -H "X-Auth-Token: TOKEN" | grep -i "x-rate-limit"

# Shopify: Verify webhook registrations
curl -s "https://YOUR-STORE.myshopify.com/admin/api/2025-01/webhooks.json" \
  -H "X-Shopify-Access-Token: TOKEN" | jq '.webhooks[] | {id, topic, address}'

# Magento: Check integration token validity
curl -s "https://your-magento.com/rest/V1/store/storeConfigs" \
  -H "Authorization: Bearer YOUR_TOKEN" | jq '.[0].code'

# Shopify: Verify inventory levels for a SKU
curl -s "https://YOUR-STORE.myshopify.com/admin/api/2025-01/inventory_levels.json?inventory_item_ids=ID" \
  -H "X-Shopify-Access-Token: TOKEN" | jq '.inventory_levels[] | {location_id, available}'

Version History & Compatibility

ComponentCurrent VersionRelease DateStatusBreaking ChangesNotes
Shopify Admin API2025-012025-01CurrentREST deprecation push toward GraphQLVersioned endpoints; 12mo min support
Shopify Admin API2024-102024-10SupportedN/APrevious stable version
Adobe Commerce2.4.72024-04CurrentAsync bulk API improvementsPHP 8.2+ required
BigCommerce REST APIV32020 (ongoing)CurrentV2 being deprecatedAlways use V3 where available
NetSuite REST API2024.22024-08CurrentN/ASOAP still supported, REST preferred
SAP S/4HANA OData24082024-08CurrentAsync OData processingx-csrf-token required for writes
D365 Business CentralAPI v2.02023 (ongoing)CurrentN/AOData v4 with Azure AD auth

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Automating order flow from Shopify/Magento/BigCommerce into any ERPYou only sell on one marketplace (Amazon/eBay)Marketplace-specific integration patterns
Need real-time inventory visibility across online + warehouseERP doesn't have an API (legacy on-premise)File-based integration (CSV/SFTP drop)
Processing 100-10,000+ orders/day< 10 orders/day and manual entry is acceptableManual order entry in ERP
Running multi-channel ecommerce (multiple storefronts)Single Shopify store with native inventoryShopify's built-in inventory management
Need automated fulfillment + tracking number pushbackERP is only for financials, not fulfillmentSimple accounting sync (journal entries only)

Cross-System Comparison

CapabilityShopifyAdobe Commerce (Magento 2)BigCommerceNotes
API StyleREST + GraphQLREST + GraphQL + SOAPREST (V2/V3) + GraphQL (Storefront)Shopify pushing GraphQL as primary
Rate Limits100-1,000 pts/s (GraphQL)Disabled by default (configurable)150-450 req/30s by planMagento most permissive; Shopify most structured
Webhook SupportExcellent (HMAC signed)Via observers + message queueGood (no signature)Shopify webhooks most reliable
Bulk OperationsGraphQL Bulk OperationsAsync bulk REST endpointsNo native bulk APIShopify best for large catalog updates
Auth ModelOAuth 2.0 / API keyOAuth 1.0a / bearer tokenOAuth + API tokenMagento's OAuth 1.0a is oldest pattern
Inventory APIinventorySetQuantities (GraphQL)POST /V1/inventory/source-itemsPUT /v3/inventory/itemsShopify: multi-location native
Order APIOrders + Fulfillment OrdersOrders + ShipmentsOrders V2/V3 + ShipmentsShopify fulfillment_orders most flexible
Multi-storeShopify Markets (single admin)Multi-website architectureMulti-storefrontMagento most flexible for B2B
Native ERP ConnectorsGlobal ERP ProgramAdobe Commerce connectorsApp marketplaceShopify most mature partner program
PaginationCursor-based (page_info)Page-based (searchCriteria)Page-based (page + limit)Cursor-based most scalable

Important Caveats

Related Units