Master Inventory Synchronization with HandledScript
Inventory synchronization is one of the most critical yet challenging aspects of e-commerce automation. Get it wrong, and you risk overselling products or disappointing customers. Get it right, and you have a competitive advantage.
The Challenge of Multi-Platform Inventory
Most growing e-commerce businesses face the same inventory synchronization challenges:
- Multiple sales channels (Shopify, Amazon, eBay, physical stores)
- Various inventory sources (warehouse management systems, drop shippers, suppliers)
- Real-time requirements (inventory updates need to happen immediately)
- Complex business rules (reserve stock, minimum quantities, channel-specific availability)
During my time at Shopify, I saw merchants struggle with inventory sync daily. They'd often resort to manual spreadsheet updates or expensive custom solutions that broke frequently.
HandledScript's Approach to Inventory Sync
HandledScript makes inventory synchronization straightforward by providing:
- Real-time triggers for inventory changes
- Batch processing for bulk updates
- Conflict resolution for simultaneous updates
- Business rule enforcement through transforms
Pattern 1: Bidirectional Shopify-WMS Sync
The most common pattern - keeping Shopify and your warehouse management system in perfect sync:
// Shopify -> WMS: When inventory changes in Shopify
workflow ShopifyToWmsSync {
trigger shopify.inventoryChanged {
store: env.SHOPIFY_STORE_URL
events: ["inventory_levels/update"]
}
transform processInventoryChange {
input: trigger.inventoryData
output: {
sku: input.inventory_item.sku,
locationId: input.location_id,
availableQuantity: input.available,
reservedQuantity: input.reserved_quantity,
timestamp: new Date().toISOString()
}
}
// Update WMS with new quantity
action wms.updateInventory {
sku: transform.processInventoryChange.sku,
quantity: transform.processInventoryChange.availableQuantity,
location: transform.processInventoryChange.locationId
}
}
// WMS -> Shopify: When inventory changes in WMS
workflow WmsToShopifySync {
trigger wms.inventoryChanged {
endpoint: env.WMS_WEBHOOK_ENDPOINT
events: ["stock.updated", "stock.allocated"]
}
transform validateWmsUpdate {
input: trigger.wmsData
output: {
sku: input.product_sku,
newQuantity: Math.max(0, input.available_quantity),
locationId: input.warehouse_location,
lastUpdated: input.updated_at
}
validate: {
quantityIsNumber: typeof input.available_quantity === 'number',
skuExists: input.product_sku && input.product_sku.length > 0
}
}
// Find Shopify product by SKU
action shopify.findProduct {
sku: transform.validateWmsUpdate.sku
}
// Update Shopify inventory
action shopify.updateInventory {
productId: action.shopify.findProduct.productId,
variantId: action.shopify.findProduct.variantId,
quantity: transform.validateWmsUpdate.newQuantity,
location: "primary"
}
}
Pattern 2: Multi-Channel Inventory Distribution
Distribute available inventory across multiple sales channels with business rules:
workflow MultiChannelInventoryDistribution {
trigger wms.inventoryChanged {
endpoint: env.WMS_WEBHOOK_ENDPOINT
events: ["stock.updated"]
}
transform calculateChannelAllocation {
input: trigger.wmsData
output: {
sku: input.product_sku,
totalAvailable: input.available_quantity,
reserveStock: Math.min(input.available_quantity * 0.1, 10), // Reserve 10% or 10 units
allocations: {
shopify: Math.floor(input.available_quantity * 0.6), // 60% to Shopify
amazon: Math.floor(input.available_quantity * 0.3), // 30% to Amazon
ebay: Math.floor(input.available_quantity * 0.1) // 10% to eBay
}
}
validate: {
hasStock: input.available_quantity > 0,
validSku: input.product_sku && input.product_sku.length > 0
}
}
// Update Shopify
action shopify.updateInventory {
sku: transform.calculateChannelAllocation.sku,
quantity: transform.calculateChannelAllocation.allocations.shopify
}
// Update Amazon
action amazon.updateInventory {
sku: transform.calculateChannelAllocation.sku,
quantity: transform.calculateChannelAllocation.allocations.amazon,
config: {
sellerId: env.AMAZON_SELLER_ID,
marketplaceId: env.AMAZON_MARKETPLACE_ID
}
}
// Update eBay
action ebay.updateInventory {
sku: transform.calculateChannelAllocation.sku,
quantity: transform.calculateChannelAllocation.allocations.ebay,
config: {
apiKey: env.EBAY_API_KEY,
token: env.EBAY_TOKEN
}
}
// Log allocation for reporting
action analytics.track {
event: "inventory_allocated",
properties: {
sku: transform.calculateChannelAllocation.sku,
totalQuantity: transform.calculateChannelAllocation.totalAvailable,
allocations: transform.calculateChannelAllocation.allocations
}
}
}
Pattern 3: Low Stock Alerting & Auto-Reordering
Automatically monitor inventory levels and trigger reordering when needed:
workflow LowStockMonitoring {
trigger schedule.cron {
expression: "0 */6 * * *" // Every 6 hours
timezone: "America/New_York"
}
// Check inventory levels across all products
action shopify.getInventoryLevels {
store: env.SHOPIFY_STORE_URL,
location: "primary"
}
transform identifyLowStock {
input: action.shopify.getInventoryLevels
output: action.shopify.getInventoryLevels.items
.filter(item => {
const minStock = item.product.tags.includes('fast-moving') ? 20 : 5;
return item.available <= minStock;
})
.map(item => ({
sku: item.sku,
currentStock: item.available,
minStockLevel: item.product.tags.includes('fast-moving') ? 20 : 5,
productTitle: item.product.title,
supplier: item.product.vendor,
reorderQuantity: item.product.tags.includes('bulk') ? 100 : 50
}))
}
// For each low stock item
forEach: transform.identifyLowStock
actions: [
// Send alert to team
{
action: "slack.sendMessage",
channel: "#inventory-alerts",
message: `🚨 Low Stock Alert: ${item.productTitle} (${item.sku}) - Only ${item.currentStock} units remaining`
},
// Auto-reorder from supplier if configured
{
condition: "item.supplier && env.AUTO_REORDER_ENABLED",
action: "supplier.createPurchaseOrder",
data: {
supplierName: item.supplier,
sku: item.sku,
quantity: item.reorderQuantity,
priority: item.currentStock === 0 ? "urgent" : "normal"
}
},
// Update product status if out of stock
{
condition: "item.currentStock === 0",
action: "shopify.updateProduct",
productSku: item.sku,
data: {
published: false,
tags: "out-of-stock"
}
}
]
}
Pattern 4: Batch Inventory Reconciliation
Daily reconciliation to ensure all systems stay in sync:
workflow DailyInventoryReconciliation {
trigger schedule.cron {
expression: "0 2 * * *" // Daily at 2 AM
timezone: "America/New_York"
}
// Get inventory from all systems
action shopify.getAllInventory {
store: env.SHOPIFY_STORE_URL
}
action wms.getAllInventory {
location: "primary"
}
action amazon.getAllInventory {
sellerId: env.AMAZON_SELLER_ID
}
// Compare and identify discrepancies
transform findDiscrepancies {
input: {
shopify: action.shopify.getAllInventory,
wms: action.wms.getAllInventory,
amazon: action.amazon.getAllInventory
}
output: {
discrepancies: input.shopify.items.map(shopifyItem => {
const wmsItem = input.wms.items.find(w => w.sku === shopifyItem.sku);
const amazonItem = input.amazon.items.find(a => a.sku === shopifyItem.sku);
return {
sku: shopifyItem.sku,
shopifyQty: shopifyItem.quantity,
wmsQty: wmsItem?.quantity || 0,
amazonQty: amazonItem?.quantity || 0,
hasDiscrepancy: shopifyItem.quantity !== wmsItem?.quantity ||
amazonItem?.quantity !== wmsItem?.quantity
};
}).filter(item => item.hasDiscrepancy)
}
}
// Generate reconciliation report
action email.send {
to: env.INVENTORY_MANAGER_EMAIL,
template: "inventory-reconciliation-report",
data: {
date: new Date().toISOString().split('T')[0],
totalDiscrepancies: transform.findDiscrepancies.discrepancies.length,
discrepancies: transform.findDiscrepancies.discrepancies
}
}
// Auto-correct minor discrepancies (within 5% or 2 units)
forEach: transform.findDiscrepancies.discrepancies
.filter(item => {
const maxQty = Math.max(item.shopifyQty, item.wmsQty, item.amazonQty);
const minQty = Math.min(item.shopifyQty, item.wmsQty, item.amazonQty);
return (maxQty - minQty) <= Math.max(2, maxQty * 0.05);
})
actions: [
{
action: "shopify.updateInventory",
sku: item.sku,
quantity: item.wmsQty // Use WMS as source of truth
},
{
action: "amazon.updateInventory",
sku: item.sku,
quantity: item.wmsQty
}
]
}
Best Practices for Inventory Sync
1. Always Use a Source of Truth
Designate one system (usually your WMS) as the authoritative source for inventory quantities.
2. Implement Conflict Resolution
Handle simultaneous updates gracefully:
transform handleInventoryConflict {
input: trigger.inventoryData
output: {
sku: input.sku,
quantity: input.conflicts?.length > 0
? Math.min(...input.conflicts.map(c => c.quantity)) // Use most conservative quantity
: input.quantity
}
}
3. Add Safety Buffers
Never sync your entire inventory - always keep a safety buffer:
transform applySafetyBuffer {
input: trigger.inventoryData
output: {
sku: input.sku,
publicQuantity: Math.max(0, input.actualQuantity - 2) // Keep 2 units as buffer
}
}
4. Monitor Performance
Track sync performance and errors:
action analytics.track {
event: "inventory_sync_completed",
properties: {
syncType: "realtime",
duration: syncEndTime - syncStartTime,
itemsUpdated: transform.inventoryUpdates.length,
errorsCount: errors.length
}
}
Conclusion
Inventory synchronization doesn't have to be complex or error-prone. With HandledScript's built-in patterns and real-time capabilities, you can build robust, automated inventory sync that scales with your business.
The key is starting simple and gradually adding complexity as your needs grow. Begin with basic bidirectional sync, then layer on business rules, multi-channel distribution, and advanced monitoring.
Ready to implement these patterns? Check out our Client-Facing Scripts documentation or join our Discord community to discuss your specific use case.
