# How to Integrate Your Inventory System with Shopify: A Manufacturer's Guide
Selling direct-to-consumer (D2C) through Shopify while running manufacturing operations creates a classic inventory nightmare. Your shop floor system says you have 500 units. Shopify says 487. A customer just ordered 20, but you actually only have 12 because 8 were allocated to a wholesale order that hasn't shipped yet.
This guide walks through integrating your manufacturing inventory system with Shopify—the right way.
Why Integration Matters for Manufacturers
Manual inventory updates between systems create three problems:
- Overselling — You sell units that don't exist, damaging customer trust
- Underselling — You hide available inventory, leaving money on the table
- Operational drag — Someone spends hours every day updating stock levels instead of making things
Real-time inventory sync isn't just convenient—it's competitive advantage.
The Integration Architecture
Most manufacturer-to-Shopify integrations follow this pattern:
`
┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Manufacturing │◄───►│ Integration │◄───►│ Shopify │
│ System (ERP) │ │ Layer (Your API)│ │ Store │
└─────────────────┘ └──────────────────┘ └─────────────┘
│ │
▼ ▼
Raw materials Orders flow
Work orders Customer data
Finished goods Shipping updates
`
The integration layer handles:
- Authentication with both systems
- Data transformation (field mapping, unit conversion)
- Sync scheduling (real-time vs. batch)
- Conflict resolution (when both systems update simultaneously)
- Error handling and retries
Step-by-Step Implementation
Step 1: Map Your Data Fields
Before writing code, document how fields translate between systems:
| Manufacturing System | Shopify | Notes | |---------------------|---------|-------| | SKU-001-BLU | sku-001-blue | Shopify uses lowercase, hyphens | | Finished Goods Warehouse | Location ID: 123456789 | Multi-location manufacturing needs mapping | | Qty Available (minus allocations) | inventory_quantity | Don't sync gross inventory | | Lot L-2025-03-A | inventory_item.inventory_levels | Track lot numbers in Shopify metafields |
Key decision: Do you sync "available to promise" inventory or "physical on-hand"? For D2C, use available-to-promise—physical inventory minus allocations for existing orders, safety stock, and wholesale commitments.
Step 2: Set Up Authentication
Shopify uses OAuth 2.0 for app authentication. Your manufacturing system will vary.
Shopify authentication flow:
`javascript
// 1. Redirect merchant to Shopify to authorize
const authUrl = https://${shop}.myshopify.com/admin/oauth/authorize? +
client_id=${API_KEY}& +
scope=read_products,write_products,read_inventory,write_inventory& +
redirect_uri=${encodeURIComponent(redirectUri)}& +
state=${nonce};
// 2. Exchange temporary code for permanent access token
const tokenResponse = await fetch(https://${shop}.myshopify.com/admin/oauth/access_token, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: API_KEY,
client_secret: API_SECRET,
code: temporaryCode
})
});
const { access_token } = await tokenResponse.json();
// Store access_token securely—this is your permanent API key
`
Store credentials securely. Never hardcode tokens. Use environment variables or a secrets manager like AWS Secrets Manager or HashiCorp Vault.
Step 3: Build the Inventory Sync
The core loop: poll your manufacturing system for changes, push updates to Shopify.
`javascript
// Manufacturing system → Shopify sync
async function syncInventoryToShopify() {
// 1. Get changed inventory from manufacturing system
const lastSync = await getLastSyncTimestamp();
const inventoryChanges = await manufacturingAPI.getInventoryChanges(lastSync);
for (const item of inventoryChanges) { try { // 2. Map manufacturing SKU to Shopify variant ID const shopifyVariantId = await getShopifyVariantId(item.sku);
if (!shopifyVariantId) {
console.warn(SKU ${item.sku} not found in Shopify);
continue;
}
// 3. Calculate available inventory const availableQty = item.onHand - item.allocated - item.safetyStock;
// 4. Update Shopify inventory level
await fetch(https://${shop}.myshopify.com/admin/api/2024-01/inventory_levels/set.json, {
method: 'POST',
headers: {
'X-Shopify-Access-Token': accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
location_id: shopifyLocationId,
inventory_item_id: shopifyVariantId,
available: Math.max(0, availableQty)
})
});
// 5. Log successful sync await logSync(item.sku, availableQty, 'success');
} catch (error) {
// 6. Handle errors—retry or alert
await logSync(item.sku, null, 'error', error.message);
await alertOperationsTeam(Sync failed for ${item.sku}: ${error.message});
}
}
// 7. Update last sync timestamp await setLastSyncTimestamp(new Date().toISOString()); }
// Run every 5 minutes
setInterval(syncInventoryToShopify, 5 60 1000);
`
Step 4: Handle Orders Flowing Back
When Shopify sells something, you need that order in your manufacturing system for fulfillment.
Shopify webhook setup:
`javascript
// Create a webhook to notify your system of new orders
await fetch(https://${shop}.myshopify.com/admin/api/2024-01/webhooks.json, {
method: 'POST',
headers: {
'X-Shopify-Access-Token': accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
webhook: {
topic: 'orders/create',
address: 'https://your-integration-server.com/webhooks/shopify/orders',
format: 'json'
}
})
});
`
Webhook handler:
`javascript
app.post('/webhooks/shopify/orders', async (req, res) => {
// 1. Verify webhook authenticity (Shopify signs webhooks)
const hmac = req.headers['x-shopify-hmac-sha256'];
const hash = crypto
.createHmac('sha256', webhookSecret)
.update(req.body, 'utf8')
.digest('base64');
if (hmac !== hash) { return res.status(401).send('Unauthorized'); }
// 2. Process the order const order = req.body;
await manufacturingAPI.createSalesOrder({
orderNumber: order.name,
customer: {
name: ${order.customer.first_name} ${order.customer.last_name},
email: order.email
},
lineItems: order.line_items.map(item => ({
sku: item.sku,
quantity: item.quantity,
unitPrice: item.price
})),
shippingAddress: order.shipping_address,
source: 'shopify'
});
res.status(200).send('OK');
});
`
Step 5: Handle Multi-Location Manufacturing
If you manufacture in multiple locations, you need a location mapping strategy:
`javascript
const locationMapping = {
'WAREHOUSE_A': {
shopifyLocationId: '123456789',
shopifyLocationName: 'Main Warehouse'
},
'FACTORY_B': {
shopifyLocationId: '987654321',
shopifyLocationName: 'West Coast Facility'
}
};
// Sync inventory for each location separately for (const [mfgLocation, shopifyLocation] of Object.entries(locationMapping)) { const inventory = await manufacturingAPI.getInventoryByLocation(mfgLocation);
for (const item of inventory) {
await updateShopifyInventory(
item.sku,
shopifyLocation.shopifyLocationId,
item.available
);
}
}
`
Common Pitfalls (And How to Avoid Them)
1. Race Conditions
Problem: Two orders come in simultaneously. Both see 10 units available. Both sell 10 units. You're now -10.
Solution: Shopify handles inventory reservation at checkout, but you need atomic updates in your manufacturing system. Use database transactions or inventory reservation patterns:
`javascript
// Reserve inventory before confirming order
await manufacturingAPI.reserveInventory({
sku: orderItem.sku,
quantity: orderItem.quantity,
orderId: order.id,
expiresAt: Date.now() + (30 60 1000) // 30 min reservation
});
`
2. API Rate Limits
Shopify allows 40 requests/second for standard plans, 80 for Shopify Plus. Bursting through these limits breaks your integration.
Solution: Implement rate limiting and batching:
`javascript
import Bottleneck from 'bottleneck';
const limiter = new Bottleneck({ minTime: 25, // 40 requests per second max maxConcurrent: 10 });
const updateShopifyInventory = limiter.wrap(async (sku, locationId, qty) => {
// Your update logic here
});
`
3. SKU Mismatches
Your manufacturing system calls it "BRACKET-V2-BLU." Shopify has "bracket-v2-blue." The sync silently fails.
Solution: Maintain a SKU mapping table and validate mappings regularly:
`javascript
// Validate all manufacturing SKUs exist in Shopify
const validationReport = await validateSkuMappings();
if (validationReport.missing.length > 0) {
await alertOperationsTeam(
Missing Shopify SKUs: ${validationReport.missing.join(', ')}
);
}
`
4. Ignoring Shopify's Inventory Levels
Shopify tracks inventory at the inventory_item level, not the product level. Updating the wrong level creates discrepancies.
Solution: Always update inventory_levels for the specific location, using the inventory_item_id from the variant:
`javascript
// Get the correct inventory_item_id
const variant = await shopifyAPI.get(/variants/${variantId}.json);
const inventoryItemId = variant.inventory_item_id;
// Update the inventory level at the specific location
await shopifyAPI.post('/inventory_levels/set.json', {
location_id: locationId,
inventory_item_id: inventoryItemId,
available: newQuantity
});
`
5. No Error Recovery
Your integration goes down. Shopify orders pile up. When it restarts, you have 50 unprocessed orders and angry customers.
Solution: Build idempotency and replay capability:
`javascript
// Store processed order IDs to avoid duplicates
const processedOrderIds = new Set(await getProcessedOrderIds());
app.post('/webhooks/shopify/orders', async (req, res) => { const orderId = req.body.id;
if (processedOrderIds.has(orderId)) { return res.status(200).send('Already processed'); }
// Process order... await manufacturingAPI.createSalesOrder(req.body); await markOrderProcessed(orderId);
res.status(200).send('OK');
});
`
Testing Your Integration
Before going live:
- Sandbox testing — Use Shopify's development stores and your manufacturing system's test environment
- Volume testing — Simulate Black Friday-level traffic (100+ orders/minute)
- Failure testing — Kill the integration mid-sync. Verify recovery.
- Edge cases — Test zero inventory, negative inventory, and discontinued SKUs
Tools and Platforms That Can Help
Don't want to build from scratch? Consider these integration platforms:
- Zapier/Make — Good for simple, low-volume integrations (under 1,000 orders/month)
- Celigo — Purpose-built for manufacturing-Shopify integrations
- SyncSpider — Specialized in e-commerce integrations with manufacturing systems
- Custom middleware — Use Node.js/ Python with libraries like
shopify-api-node
Measuring Success
Track these metrics:
- Sync latency — Time from inventory change to Shopify update (target: < 5 minutes)
- Error rate — Failed syncs / total syncs (target: < 0.1%)
- Oversell incidents — Shopify orders for unavailable inventory (target: 0)
- Manual interventions — Human fixes required per week (target: < 5)
Final Thoughts
Inventory integration isn't a one-time project—it's an ongoing operational system. Start simple (one-way sync of inventory levels), get it stable, then add bidirectional order flow. Monitor religiously. Fix errors fast. Your customers will thank you with repeat business.
Need help finding the right APIs? Browse the ForgeDirectory manufacturing API catalog for authentication details, endpoint references, and integration patterns for popular inventory and ERP systems.