Manual order fulfillment doesn’t scale. When your WooCommerce store grows beyond 50 orders per day, the manual workflow — check WooCommerce, enter order into ERP, pick and pack, manually update tracking — becomes a bottleneck. ERP-driven automation eliminates this bottleneck by creating a continuous pipeline from “order placed” to “delivered.”
The Automated Fulfillment Pipeline
Order Placed (WooCommerce)
→ Fraud Check
→ Payment Verification
→ Order Created in ERP
→ Inventory Reserved
→ Pick List Generated
→ Warehouse Picks & Packs
→ Shipping Label Created
→ Tracking Number → WooCommerce
→ Customer Notification
→ Order Marked "Completed"
Each step happens automatically. Human intervention is only needed for exceptions (fraud flags, stock-outs, address issues).
Step 1: Order Capture
WooCommerce webhooks trigger the pipeline in real-time:
// Webhook handler
app.post('/webhooks/order-created', async (req, res) => {
const order = req.body;
// Quick validation
if (order.status !== 'processing') {
return res.status(200).send('Skipped: not processing');
}
// Queue for processing
await orderQueue.add('process-order', {
orderId: order.id,
orderData: order,
receivedAt: new Date().toISOString()
});
res.status(200).send('Queued');
});
Step 2: Fraud and Validation Check
Before sending to the ERP, validate the order:
async function validateOrder(order) {
const issues = [];
// Address validation
if (!order.shipping.postcode || order.shipping.postcode.length < 3) {
issues.push('Invalid shipping postcode');
}
// High-value order flag
if (parseFloat(order.total) > 1000) {
issues.push('High-value order - manual review');
}
// Duplicate order check (same customer, same items, within 5 minutes)
const recentOrders = await db.query(
'SELECT * FROM processed_orders WHERE customer_email = ? AND created_at > DATE_SUB(NOW(), INTERVAL 5 MINUTE)',
[order.billing.email]
);
if (recentOrders.length > 0) {
issues.push('Possible duplicate order');
}
return { valid: issues.length === 0, issues };
}
Step 3: ERP Order Creation
Transform and push the order to your ERP:
async function createERPOrder(wcOrder) {
// Find or create customer
let customer = await erp.findCustomer({ email: wcOrder.billing.email });
if (!customer) {
customer = await erp.createCustomer({
name: ${wcOrder.billing.first_name} ${wcOrder.billing.last_name},
email: wcOrder.billing.email,
phone: wcOrder.billing.phone,
address: mapAddress(wcOrder.shipping)
});
}
// Create sales order
const salesOrder = await erp.createSalesOrder({
customerId: customer.id,
externalRef: WC-${wcOrder.id},
orderDate: wcOrder.date_created,
lines: wcOrder.line_items.map(item => ({
sku: item.sku,
quantity: item.quantity,
unitPrice: parseFloat(item.price),
discount: parseFloat(item.total) < (item.quantity * parseFloat(item.price))
? (item.quantity * parseFloat(item.price)) - parseFloat(item.total)
: 0
})),
shippingMethod: wcOrder.shipping_lines[0]?.method_title,
shippingCost: parseFloat(wcOrder.shipping_total),
notes: wcOrder.customer_note || ''
});
// Store mapping
await db.query(
'INSERT INTO order_mapping (wc_order_id, erp_order_id, status) VALUES (?, ?, ?)',
[wcOrder.id, salesOrder.id, 'created']
);
return salesOrder;
}
Step 4: Inventory Reservation
The ERP should reserve (commit) inventory immediately when the sales order is created. This prevents overselling between order creation and physical picking.
Most ERPs handle this automatically when a sales order is created. Verify this with your ERP — some require explicit reservation calls.
Step 5: Shipping Label Generation
Integrate with your shipping carrier’s API to auto-generate labels:
async function generateShippingLabel(erpOrder, shippingAddress) {
// Example: ShipStation API
const shipment = await shipstation.createShipment({
orderId: erpOrder.externalRef,
carrierCode: selectCarrier(shippingAddress, erpOrder.totalWeight),
serviceCode: selectService(shippingAddress),
shipTo: {
name: ${shippingAddress.first_name} ${shippingAddress.last_name},
street1: shippingAddress.address_1,
street2: shippingAddress.address_2,
city: shippingAddress.city,
state: shippingAddress.state,
postalCode: shippingAddress.postcode,
country: shippingAddress.country
},
weight: { value: erpOrder.totalWeight, units: 'grams' },
dimensions: calculatePackageDimensions(erpOrder.lines)
});
return {
trackingNumber: shipment.trackingNumber,
labelUrl: shipment.labelData,
carrier: shipment.carrierCode
};
}
Step 6: Tracking Number Back to WooCommerce
Update the WooCommerce order with tracking information:
async function updateWooCommerceTracking(wcOrderId, tracking) {
// Update order meta with tracking info
await wooApi.put(orders/${wcOrderId}, {
status: 'completed',
meta_data: [
{ key: '_tracking_number', value: tracking.trackingNumber },
{ key: '_tracking_carrier', value: tracking.carrier },
{ key: '_tracking_url', value: tracking.trackingUrl }
]
});
// Add order note
await wooApi.post(orders/${wcOrderId}/notes, {
note: Order shipped via ${tracking.carrier}. Tracking: ${tracking.trackingNumber},
customer_note: true // Visible to customer
});
}
Exception Handling
Not every order flows smoothly. Build handlers for common exceptions:
| Exception | Action |
|---|---|
| Item out of stock | Hold order, notify customer, offer alternative |
| Address undeliverable | Hold order, notify customer to update address |
| Payment failed post-auth | Cancel ERP order, release inventory |
| Fraud flag | Hold for manual review, don’t ship |
| Shipping label failure | Retry with alternative carrier |
| ERP down | Queue order, retry when ERP recovers |
Monitoring
Track your pipeline health:
// Hourly pipeline metrics
const metrics = {
ordersReceived: await countOrders('received', lastHour),
ordersProcessed: await countOrders('processed', lastHour),
ordersShipped: await countOrders('shipped', lastHour),
ordersFailed: await countOrders('failed', lastHour),
avgProcessingTime: await avgTime('received', 'shipped', lastHour),
pendingOrders: await countOrders('pending')
};
Alert thresholds:
- Processing time > 30 minutes: investigate
- Failed orders > 5% of total: critical alert
- Pending queue > 100 orders: capacity issue
Conclusion
ERP-driven order automation transforms fulfillment from a manual, error-prone process into a reliable pipeline. The investment in building this pipeline pays for itself quickly — fewer errors, faster shipping, happier customers, and team time freed from data entry. Start with the core flow (order to ERP, tracking back to WooCommerce), then add shipping label generation and exception handling iteratively.
Last modified: April 3, 2026
United States / English
Slovensko / Slovenčina
Canada / Français
Türkiye / Türkçe