Enterprise resource planning systems hold the operational truth for inventory, pricing, customer accounts, and financial records. Shopify holds the commercial truth for orders, payments, and storefront behavior. When these two systems disagree, merchants ship from inventory that does not exist, publish prices that contradict finance records, and process orders that the ERP has already fulfilled through a different channel.
Shopify ERP integration architecture is the discipline of designing the data flows, conflict resolution rules, transformation layers, and fault-tolerance mechanisms that keep these two systems consistent without creating tight coupling that breaks both when either changes.
This guide covers the complete technical architecture for ERP Shopify sync: integration pattern selection, field mapping and data transformation design, order flow from Shopify to ERP, inventory and pricing sync from ERP to Shopify, conflict resolution under concurrent updates, error handling, and the specific considerations for SAP, NetSuite, and Oracle integrations with Shopify Plus.
What Is Shopify ERP Integration Architecture?
Shopify ERP integration architecture is the structured design of how data moves between Shopify’s commerce platform and an enterprise resource planning system, including the middleware layer that handles transformation, routing, error recovery, and consistency guarantees between the two systems.
Unlike point-to-point API integrations that connect two systems directly, ERP integrations require an explicit architecture because the two systems have fundamentally different data models, update frequencies, consistency requirements, and failure modes. Shopify operates in near-real-time with sub-second order creation. ERPs like SAP S/4HANA, Oracle NetSuite, and Microsoft Dynamics process financial records in batch windows with strict transactional integrity requirements.
The three non-negotiable design decisions in every enterprise ERP Shopify integration are: which system is the source of truth for each data domain, how conflicts are detected and resolved when both systems update the same record simultaneously, and how the integration recovers from partial failures without data loss or duplication.
The fault-tolerant Shopify integration design principles that apply to all Shopify integrations are especially critical in ERP contexts because ERP data errors propagate to financial records, fulfillment workflows, and compliance reporting — domains where incorrect data has legal and operational consequences.
Choosing the Right ERP Integration Pattern
The integration pattern determines the latency, reliability, and operational complexity of your ERP sync. Each pattern suits a different combination of ERP capability, data domain, and update frequency.
| Integration Pattern | Data Flow | Latency | Best For |
| Real-time webhook push | Shopify to ERP | Seconds | Order creation, payment events |
| Scheduled batch sync | Bidirectional | Minutes to hours | Inventory, product catalog updates |
| Event-driven middleware | Bidirectional via bus | Near real-time | Complex multi-system fan-out |
| API polling (ERP pull) | ERP pulls Shopify | Minutes | Legacy ERP with no webhook support |
| ETL pipeline | ERP to Shopify | Hours | Bulk product and price imports |
Real-Time Webhook Push for Order Data
Shopify webhooks are the highest-fidelity mechanism for pushing order events to an ERP in near real-time. When a customer places an order, Shopify fires an orders/create webhook within seconds. Your middleware receives the payload, transforms it to the ERP’s data model, and submits it to the ERP’s order management API.
This pattern works for order creation, payment capture, fulfillment updates, and refund events — any Shopify-originated event that the ERP needs to know about within minutes. It does not work for ERP-originated updates (inventory changes, price adjustments) because ERPs do not natively emit webhooks to external systems.
Scheduled Batch Sync for Inventory and Pricing
Inventory levels and pricing structures originate in the ERP and must propagate to Shopify on a defined schedule. A batch sync job runs on a cron schedule, queries the ERP for changed records since the last sync timestamp, transforms the data, and pushes updates to Shopify via the Admin API.
Batch frequency depends on business requirements. Inventory sync for fast-moving consumer goods may need to run every 5-15 minutes. Pricing sync for enterprise B2B catalogs may run nightly. The sync interval must be shorter than the time window in which a stock-out or incorrect price causes a customer-facing problem.
Pair batch sync architecture with Shopify queue infrastructure to handle the high-volume write operations that bulk inventory updates generate against Shopify’s Admin API rate limits.
Middleware Architecture for Shopify ERP Integration
Direct point-to-point integration between Shopify and an ERP creates tight coupling: every schema change in either system requires changes in the integration code. A middleware layer decouples the two systems, handles data transformation, and provides a central point for error handling, retry logic, and audit logging.
The Three-Layer Middleware Model
Production Shopify ERP integration architecture uses three middleware layers:
- Ingestion layer: receives events from Shopify webhooks or ERP polling, validates schema, and enqueues for processing. Returns acknowledgment immediately without waiting for downstream processing.
- Transformation layer: maps Shopify data models to ERP data models (and vice versa), applies business rules, resolves identifiers, and normalizes field values. Runs as a stateless worker consuming from the ingestion queue.
- Delivery layer: submits transformed data to the target system’s API, handles rate limiting, retries on transient failures, and routes permanent failures to a dead-letter queue with alerting.
// Middleware: three-layer ERP integration pipeline
// Layer 1: Ingestion — Shopify webhook receiver
app.post('/webhooks/orders', express.raw({ type: '*/*' }), async (req, res) => {
if (!verifyShopifyHmac(req.body, req.headers['x-shopify-hmac-sha256'])) {
return res.status(401).send('Unauthorized');
}
// Enqueue raw payload immediately — return 200 to Shopify
await ingestionQueue.add('shopify.order.created', {
raw: req.body.toString(),
shop: req.headers['x-shopify-shop-domain'],
webhookId: req.headers['x-shopify-webhook-id'],
topic: req.headers['x-shopify-topic'],
});
res.status(200).send('OK');
});
// Layer 2: Transformation — maps Shopify order to ERP sales order
const transformWorker = new Worker('ingestion', async (job) => {
const shopifyOrder = JSON.parse(job.data.raw);
const erpOrder = transformShopifyOrderToERP(shopifyOrder);
await deliveryQueue.add('erp.order.create', {
erpPayload: erpOrder,
shopifyOrderId: shopifyOrder.id,
shop: job.data.shop,
idempotencyKey: `order:${shopifyOrder.id}:${job.data.shop}`,
});
}, { connection, concurrency: 10 });
// Layer 3: Delivery — submits to ERP API with retry and idempotency
const deliveryWorker = new Worker('delivery', async (job) => {
const { erpPayload, idempotencyKey } = job.data;
// Check idempotency before submitting to ERP
const alreadySubmitted = await redis.set(
`erp:submitted:${idempotencyKey}`, '1', { NX: true, EX: 86400 }
);
if (!alreadySubmitted) return { status: 'duplicate' };
await submitToERP(erpPayload);
}, {
connection,
concurrency: 5,
limiter: { max: 10, duration: 1000 }, // ERP API rate limit
});
|
The idempotency check at the delivery layer is critical for ERP integrations. ERP systems do not always support idempotent API calls natively. Submitting the same order twice to SAP or NetSuite creates duplicate sales orders that require manual reconciliation. The Redis NX check prevents this at the middleware layer before the ERP call is made.
Order Data Flow: Shopify to ERP
Order data is the highest-priority, lowest-latency data domain in any ERP Shopify sync. When a customer places an order on Shopify, the ERP needs it within minutes to initiate picking, packing, shipping, and financial recording. A delayed order sync delays fulfillment and revenue recognition simultaneously.
Field Mapping: Shopify Order to ERP Sales Order
Shopify and ERP systems use different identifiers, data structures, and field naming conventions. A field mapping specification must be defined and maintained as a versioned document, not embedded implicitly in integration code. The following mapping covers the core fields required for most ERP sales order creation:
// Field mapping: Shopify Order -> ERP Sales Order
// Adjust field names to match your specific ERP's API schema
function transformShopifyOrderToERP(shopifyOrder) {
return {
// ERP Sales Order Header
externalOrderId: shopifyOrder.id.toString(),
externalOrderRef: shopifyOrder.name, // e.g. #1234
orderDate: shopifyOrder.created_at,
currency: shopifyOrder.currency,
totalAmount: parseFloat(shopifyOrder.total_price),
taxAmount: parseFloat(shopifyOrder.total_tax),
shippingAmount: parseFloat(shopifyOrder.total_shipping_price_set
?.shop_money?.amount || 0),
// Customer / Bill-To
customer: {
externalId: shopifyOrder.customer?.id?.toString(),
email: shopifyOrder.email,
firstName: shopifyOrder.billing_address?.first_name,
lastName: shopifyOrder.billing_address?.last_name,
company: shopifyOrder.billing_address?.company,
address1: shopifyOrder.billing_address?.address1,
address2: shopifyOrder.billing_address?.address2,
city: shopifyOrder.billing_address?.city,
province: shopifyOrder.billing_address?.province_code,
country: shopifyOrder.billing_address?.country_code,
zip: shopifyOrder.billing_address?.zip,
},
// Ship-To
shipTo: {
firstName: shopifyOrder.shipping_address?.first_name,
lastName: shopifyOrder.shipping_address?.last_name,
address1: shopifyOrder.shipping_address?.address1,
city: shopifyOrder.shipping_address?.city,
province: shopifyOrder.shipping_address?.province_code,
country: shopifyOrder.shipping_address?.country_code,
zip: shopifyOrder.shipping_address?.zip,
},
// Line Items
lineItems: shopifyOrder.line_items.map(item => ({
sku: item.sku,
erpItemCode: mapShopifySkuToERPCode(item.sku), // Lookup table
description: item.title,
quantity: item.quantity,
unitPrice: parseFloat(item.price),
discountAmount: parseFloat(item.total_discount),
taxable: item.taxable,
})),
};
}
|
SKU and Product Identifier Reconciliation
The most common failure point in Shopify-to-ERP order sync is identifier mismatch. Shopify identifies products by variant ID and SKU. ERPs identify inventory items by internal item codes, material numbers (SAP), or inventory IDs (NetSuite) that have no relationship to Shopify’s identifiers.
Maintain a cross-reference table in your middleware database that maps Shopify variant IDs and SKUs to ERP item codes. Build and validate this table during the initial integration setup, and implement a webhook handler for products/update events that keeps it current when SKUs change in Shopify. An order line item with a SKU that has no ERP mapping should fail loudly, not silently drop the line item.
Inventory Sync: ERP to Shopify
Inventory is the most operationally sensitive data domain in any ERP integration. Shopify displaying inventory quantities that the ERP has already allocated to another channel causes overselling. Shopify displaying inventory quantities that are too conservative causes lost sales. The sync architecture must balance accuracy against the cost of frequent API calls.
Delta Sync with ERP Change Journals
Full inventory sync — querying the ERP for all inventory levels on every sync cycle — does not scale beyond a few thousand SKUs. For catalogs with tens of thousands of items, a delta sync pattern queries only records that have changed since the last successful sync timestamp.
Most enterprise ERPs maintain change journals or audit logs that record every inventory movement with a timestamp. SAP’s Material Documents (MKPF/MSEG tables), NetSuite’s Inventory Adjustments, and Oracle’s MTL_MATERIAL_TRANSACTIONS all provide this capability. Query the journal for movements since the last sync watermark rather than querying current stock levels for every item.
// Delta inventory sync: query ERP for changes since last watermark
async function syncInventoryFromERP(shop) {
const syncKey = `erp:inventory:watermark:${shop}`;
const lastSyncedAt = await redis.get(syncKey) || '1970-01-01T00:00:00Z';
// Query ERP change journal for inventory movements since watermark
// Adjust query to match your ERP's change tracking API
const changedItems = await erpClient.getInventoryChanges({
since: lastSyncedAt,
warehouseId: process.env.ERP_WAREHOUSE_ID,
});
if (changedItems.length === 0) return { synced: 0 };
// Transform ERP item codes to Shopify inventory item IDs
const updates = await Promise.all(
changedItems.map(async (item) => {
const shopifyItemId = await lookupShopifyInventoryItemId(
shop, item.erpItemCode
);
if (!shopifyItemId) {
await logUnmappedItem(shop, item.erpItemCode);
return null;
}
return {
inventory_item_id: shopifyItemId,
location_id: process.env.SHOPIFY_LOCATION_ID,
available: item.availableQuantity,
};
})
);
const validUpdates = updates.filter(Boolean);
// Shopify bulk inventory update: max 100 items per API call
for (let i = 0; i < validUpdates.length; i += 100) {
const batch = validUpdates.slice(i, i + 100);
await shopifyAdmin.post(
`/inventory_bulk_adjust_quantity_at_location.json`,
{ location_id: process.env.SHOPIFY_LOCATION_ID, changes: batch }
);
// Respect Shopify API rate limits between batches
await new Promise(r => setTimeout(r, 500));
}
// Advance watermark to current time after successful sync
await redis.set(syncKey, new Date().toISOString());
return { synced: validUpdates.length };
}
|
The watermark pattern is the most reliable mechanism for delta sync because it survives integration restarts without duplicating updates or missing records. Always advance the watermark after a successful sync, never before. Advancing before processing means a crash mid-sync advances the watermark past records that were never written to Shopify.
Pricing Sync: ERP to Shopify
Enterprise pricing structures in ERPs are significantly more complex than Shopify’s native price model. SAP condition records support customer-group-specific pricing, quantity break discounts, validity periods, and currency-specific prices. NetSuite price levels support tiered pricing per customer category. Shopify’s native pricing supports a single price per variant with optional compare-at price and basic discount codes.
Mapping ERP Price Structures to Shopify
The mapping strategy depends on which Shopify pricing feature set is available. Standard Shopify supports one price per variant. Shopify Plus B2B supports price lists that assign customer-specific prices and payment terms. For enterprise merchants on Shopify Plus with B2B enabled, ERP customer group pricing maps to Shopify B2B price lists.
// ERP price sync: maps SAP condition records to Shopify price lists
// Requires Shopify Plus with B2B enabled
// Uses Shopify GraphQL Admin API 2025-04
const PRICE_LIST_MUTATION = `
mutation priceListFixedPricesAdd($id: ID!, $prices: [PriceListPriceInput!]!) {
priceListFixedPricesAdd(priceListId: $id, prices: $prices) {
prices { compareAtPrice { amount } price { amount } variant { id sku } }
userErrors { field message }
}
}
`;
async function syncERPPriceListToShopify(shop, erpPriceGroup, shopifyPriceListId) {
// Fetch active condition records for this customer group from ERP
const erpPrices = await erpClient.getConditionRecords({
salesOrg: process.env.ERP_SALES_ORG,
customerGroup: erpPriceGroup,
validOn: new Date().toISOString(),
});
// Map ERP item codes to Shopify variant GIDs
const prices = await Promise.all(
erpPrices.map(async (record) => {
const variantGid = await lookupShopifyVariantGid(shop, record.materialCode);
if (!variantGid) return null;
return {
variantId: variantGid,
price: { amount: record.netPrice.toString(), currencyCode: record.currency },
compareAtPrice: record.listPrice
? { amount: record.listPrice.toString(), currencyCode: record.currency }
: null,
};
})
);
const validPrices = prices.filter(Boolean);
// Submit to Shopify in batches of 250 (GraphQL limit)
for (let i = 0; i < validPrices.length; i += 250) {
await shopifyGraphQL(shop, PRICE_LIST_MUTATION, {
id: shopifyPriceListId,
prices: validPrices.slice(i, i + 250),
});
}
}
|
For standard Shopify merchants without B2B pricing, ERP price sync maps to Shopify’s compare_at_price and price fields on variants. Quantity break pricing and customer group pricing must be approximated using Shopify discount codes or automatic discounts, which have significant functional limitations compared to ERP condition record granularity.
Shopify SAP Integration: Specific Considerations
Shopify SAP integration presents the highest technical complexity of any common ERP pairing because SAP S/4HANA and SAP ECC expose data through multiple incompatible interface layers: RFC/BAPI calls, IDocs, OData services (SAP Gateway), and the newer SAP Business Accelerator Hub APIs.
SAP Interface Layer Selection
The SAP interface layer you choose determines your middleware architecture. OData services via SAP Gateway are the preferred integration point for new Shopify-SAP integrations. They expose RESTful endpoints that are easier to consume from Node.js or Python middleware than RFC/BAPI calls, which require the SAP NetWeaver RFC SDK or JCo connector.
- SAP OData (preferred): RESTful, standard HTTP client, supports filtering and delta queries. Available for most standard SAP business objects including sales orders, inventory, customer master, and pricing.
- SAP IDoc: asynchronous, batch-oriented, requires an IDoc adapter (MuleSoft, Dell Boomi, SAP PI/PO). Use when your SAP instance does not expose OData for the required objects.
- SAP BAPI via RFC: synchronous, requires RFC library, high coupling to SAP internals. Avoid for new integrations; maintain only where existing BAPI-based integrations are already in production.
For Shopify Plus merchants running SAP S/4HANA Cloud, the SAP Integration Suite provides pre-built integration flows for order management, inventory, and customer master data that reduce custom middleware development. Evaluate these against your specific data model requirements before building custom transformation logic.
Conflict Resolution in Bidirectional ERP Shopify Sync
Bidirectional sync creates the possibility of conflicting updates: a product price updated in the ERP at the same moment a merchant manually updates it in Shopify Admin. Without explicit conflict resolution logic, the last write wins, which may not be the correct business outcome.
Source of Truth Assignment by Data Domain
The most reliable conflict prevention strategy is assigning a definitive source of truth for each data domain and enforcing it architecturally:
// Source of truth configuration per data domain
// Enforced by middleware: only the authoritative system's
// updates are accepted for each domain
const SOURCE_OF_TRUTH = {
// ERP is authoritative for operational data
inventory: 'ERP', // Shopify inventory is always ERP-derived
pricing: 'ERP', // Price changes in Shopify are overwritten on next sync
productCatalog: 'ERP', // Product master data originates in ERP
customerMaster: 'ERP', // Customer records synced from ERP CRM
// Shopify is authoritative for commerce data
orders: 'SHOPIFY', // Orders flow Shopify -> ERP only
storeCreditNotes: 'SHOPIFY', // Refunds and credit notes from Shopify
abandonedCarts: 'SHOPIFY', // No ERP equivalent
reviews: 'SHOPIFY', // No ERP equivalent
// Bidirectional with timestamp-based last-write-wins
customerAddresses: 'TIMESTAMP', // Shipping address changes can originate either side
};
// Conflict check for timestamp-based domains
async function resolveConflict(domain, shopifyRecord, erpRecord) {
if (SOURCE_OF_TRUTH[domain] === 'ERP') return erpRecord;
if (SOURCE_OF_TRUTH[domain] === 'SHOPIFY') return shopifyRecord;
// Timestamp-based: most recent update wins
const shopifyUpdated = new Date(shopifyRecord.updated_at);
const erpUpdated = new Date(erpRecord.lastModified);
return shopifyUpdated > erpUpdated ? shopifyRecord : erpRecord;
}
|
Preventing Sync Loops
Bidirectional sync creates the risk of sync loops: the middleware writes a price update from the ERP to Shopify, which triggers a products/update webhook, which the middleware ingests and attempts to sync back to the ERP, which triggers another ERP update, repeating indefinitely.
Prevent sync loops by tagging every write made by the integration middleware with a integration_source identifier. In Shopify, use metafields or the note_attributes field on orders to mark middleware-originated writes. In your webhook handler, check for this tag before routing the event to the ERP. If the tag is present, the update originated from the middleware — skip the sync to prevent the loop.
The async Shopify architecture event-driven patterns complement this approach by using domain events with explicit origin metadata that each consumer can inspect before deciding whether to process or discard an event.
Error Handling and Data Recovery in ERP Integrations
ERP integrations fail at a higher rate than simpler Shopify app integrations because they involve two complex systems with their own maintenance windows, schema versions, and API rate limits. A robust error handling architecture is not optional — it is the difference between an integration that recovers automatically and one that requires manual intervention on every failure.
Error Classification for ERP Integration
Classify every integration error into one of three categories to determine the correct handling strategy:
- Transient errors: network timeouts, temporary ERP unavailability, API rate limit 429s. Retry with exponential backoff. These resolve without human intervention.
- Data errors: unmapped SKU, invalid field value, missing required field. Do not retry — the same payload will fail again. Route to DLQ with detailed context for human review.
- System errors: ERP authentication failure, middleware database unavailability, broken schema version. Alert immediately. Pause all integration processing for the affected system until the root cause is resolved.
// Error classification and routing for ERP integration
async function handleERPDeliveryError(job, error) {
const errorContext = {
jobId: job.id,
shop: job.data.shop,
shopifyOrderId: job.data.shopifyOrderId,
errorMessage: error.message,
errorCode: error.code,
attemptsMade: job.attemptsMade,
timestamp: new Date().toISOString(),
};
// Transient: retry automatically
if (isTransientError(error)) {
if (job.attemptsMade < 5) {
throw error; // BullMQ retries with exponential backoff
}
// Exhausted retries: escalate to DLQ
await deadLetterQueue.add('erp.delivery.failed', errorContext);
await alertOpsTeam('ERP delivery retry exhausted', errorContext);
return;
}
// Data error: route to DLQ immediately, no retry
if (isDataError(error)) {
await deadLetterQueue.add('erp.data.error', errorContext);
await alertDataTeam('ERP field mapping error', errorContext);
return;
}
// System error: pause queue and alert immediately
if (isSystemError(error)) {
await deliveryQueue.pause();
await alertOpsTeam('CRITICAL: ERP system error — queue paused', errorContext);
return;
}
}
function isTransientError(err) {
return [408, 429, 500, 502, 503, 504].includes(err.statusCode)
|| err.code === 'ECONNRESET'
|| err.code === 'ETIMEDOUT';
}
function isDataError(err) {
return [400, 404, 422].includes(err.statusCode);
}
function isSystemError(err) {
return [401, 403].includes(err.statusCode);
}
|
The dead-letter queue for ERP integrations must include full payload context and error detail. When a data team investigates a failed order sync hours after the fact, they need the original Shopify order payload, the transformed ERP payload, the exact error response from the ERP API, and the timestamp of every retry attempt. Logging only the error message is insufficient for ERP data error debugging.
Shopify Plus and Enterprise ERP Integration Considerations
Shopify Plus unlocks several platform capabilities that significantly simplify enterprise ERP Shopify integration architecture compared to standard Shopify:
- B2B price lists and company accounts: native Shopify support for customer-group pricing that maps directly to ERP customer class pricing structures, reducing the need for complex middleware price transformation logic.
- Higher API rate limits: Shopify Plus provides a 4 requests per second REST rate limit (double the standard 2 RPS) and a higher GraphQL cost budget, enabling faster bulk inventory and product sync operations.
- Custom checkout: Checkout Extensibility allows embedding ERP validation logic (credit limit checks, tax exemption verification, backorder confirmation) directly in the checkout flow via checkout extensions.
- Flow automation: Shopify Flow can trigger integration middleware endpoints on complex conditional logic without custom webhook registration, simplifying event-driven ERP sync triggers.
For Shopify Plus merchants integrating with SAP, NetSuite, or Oracle, the integration complexity justifies a formal integration design document that specifies source of truth assignments, field mapping tables, sync schedules, error SLAs, and rollback procedures for each data domain before any code is written.
Review Shopify vs Shopify Plus infrastructure capabilities to confirm which API features and platform limits apply to your merchant tier before designing the integration architecture, as several critical ERP integration capabilities are Plus-exclusive.
Conclusion
Shopify ERP integration architecture is a system design problem, not an API connection problem. The three most critical architectural decisions that determine whether an integration succeeds in production are:
- Assign a definitive source of truth for every data domain before writing code. Inventory and pricing originate in the ERP and flow to Shopify. Orders originate in Shopify and flow to the ERP. Enforcing these assignments architecturally prevents the bidirectional conflict and sync loop problems that cause most ERP integration failures in production.
- Build the middleware layer with three explicit tiers: ingestion, transformation, and delivery. Keeping these tiers separate lets you scale, debug, and modify each independently. A transformation logic change should never require touching the webhook ingestion code. A delivery rate limit change should never require reprocessing already-transformed payloads.
- Classify every error as transient, data, or system — and handle each differently. Retrying a data error wastes resources and delays the alert that would fix it. Not retrying a transient error causes unnecessary data loss. Pausing processing on a system error prevents a queue backlog that compounds when the system recovers.
Start every ERP integration engagement with a data domain mapping exercise before writing a single line of integration code. Define source of truth, sync direction, sync frequency, and conflict resolution strategy per domain in a shared document. The architectural decisions made in that document determine 80% of the integration’s long-term operational cost. If you need guidance designing a production-grade Shopify ERP integration architecture, review the high-traffic Shopify architecture patterns that provide the infrastructure foundation every enterprise integration requires.
Frequently Asked Questions
What is Shopify ERP integration architecture?
Shopify ERP integration architecture is the structured design of how data moves between Shopify’s commerce platform and an enterprise resource planning system, including the middleware layer that handles transformation, routing, error recovery, and consistency guarantees. It covers which system is the source of truth for each data domain, how conflicts are resolved when both systems update the same record, and how the integration recovers from partial failures without data loss or duplication.
What is the best pattern for syncing orders from Shopify to an ERP?
The best pattern for syncing orders from Shopify to an ERP is real-time webhook push with async middleware processing. When a customer places an order, Shopify fires an orders/create webhook. Your middleware receives the payload, validates the HMAC, enqueues it immediately, and returns 200 to Shopify. A transformation worker maps the Shopify order fields to the ERP’s sales order schema. A delivery worker submits the transformed payload to the ERP API with idempotency controls to prevent duplicate order creation.
How do you prevent sync loops in bidirectional Shopify ERP integration?
Prevent sync loops by tagging every write made by the integration middleware with an integration_source identifier. In Shopify, use metafields or note_attributes to mark middleware-originated writes. In your webhook handler, check for this tag before routing the event to the ERP. If the tag is present, the update originated from the middleware and should be skipped to prevent it from triggering another ERP update that creates an infinite sync cycle.
How does Shopify SAP integration differ from other ERP integrations?
Shopify SAP integration is more complex than other ERP pairings because SAP exposes data through multiple incompatible interface layers: OData services via SAP Gateway, IDoc files, and RFC/BAPI calls. New integrations should use SAP OData services, which provide RESTful endpoints consumable by standard HTTP clients. Legacy integrations may require SAP PI/PO or an integration platform such as MuleSoft or Dell Boomi to handle IDoc processing. SAP S/4HANA Cloud users should evaluate SAP Integration Suite pre-built flows before building custom middleware.
How do you sync ERP inventory to Shopify at scale?
Sync ERP inventory to Shopify at scale using a delta sync pattern with a watermark timestamp. Instead of querying current stock levels for every SKU on every sync cycle, query the ERP’s change journal (SAP Material Documents, NetSuite Inventory Adjustments, or Oracle MTL_MATERIAL_TRANSACTIONS) for inventory movements since the last successful sync watermark. Transform changed items to Shopify inventory updates and submit in batches of up to 100 items using the Shopify bulk inventory adjustment API. Advance the watermark only after a successful sync to prevent skipping records on middleware restarts.
