This integration playbook covers the end-to-end data flow between ERP systems and warehouse management systems (WMS) or third-party logistics (3PL) providers. It addresses both enterprise WMS platforms (Manhattan Active WM, Blue Yonder, Oracle WMS Cloud, SAP EWM) and cloud-based 3PL APIs (ShipBob, ShipStation, Flexport). The playbook applies regardless of which ERP system is the source — SAP S/4HANA, Oracle ERP Cloud, NetSuite, Microsoft Dynamics 365, or others.
| System | Role | API Surface | Direction |
|---|---|---|---|
| ERP (SAP, Oracle, NetSuite, D365) | Order master, inventory financial ledger, item master | Varies by ERP | Outbound (orders) / Inbound (confirmations) |
| Manhattan Active WM | Cloud-native WMS — allocation, pick/pack/ship | REST API (OAuth 2.0) | Inbound (orders) / Outbound (shipments) |
| Blue Yonder WMS | Enterprise WMS — wave planning, labor mgmt | MOCA, REST, EDI | Inbound (orders) / Outbound (shipments) |
| Oracle WMS Cloud | Cloud WMS — receiving, putaway, fulfillment | REST API (XML/JSON) | Inbound (orders) / Outbound (shipments) |
| SAP EWM | SAP-native WMS — embedded or decentralized | OData, IDoc, RFC/BAPI | Bidirectional with S/4HANA |
| ShipBob / ShipStation / Flexport | 3PL fulfillment APIs | REST API (API key) | Inbound (orders) / Outbound (tracking) |
| Middleware (MuleSoft, Boomi, Celigo) | Integration orchestrator | N/A | Orchestrator |
| API Surface | Protocol | Best For | Auth Method | Real-time? | Bulk? | EDI Support |
|---|---|---|---|---|---|---|
| Manhattan Active WM REST | HTTPS/JSON | Order release, inventory queries, shipment confirmation | OAuth 2.0 client credentials | Yes | Limited (pagination) | No (separate EDI gateway) |
| Blue Yonder MOCA | Proprietary TCP | Direct WMS operations, wave management | Session-based | Yes | Yes | N/A |
| Blue Yonder Integration API | HTTPS/JSON | Modern integrations, event-driven | API gateway token | Yes | Yes | Via SPS Commerce |
| Oracle WMS Cloud REST | HTTPS/XML+JSON | Inbound/outbound shipments, inventory | OAuth 2.0 | Yes | Via file import | Via Oracle Integration Cloud |
| SAP EWM IDoc | RFC/ALE | High-volume batch order/confirmation exchange | SAP system auth | Near-real-time | Yes | Via SAP PI/PO |
| SAP EWM OData | HTTPS/JSON | Modern real-time queries, mobile apps | OAuth 2.0 / SAML | Yes | Limited | No |
| ShipBob REST API | HTTPS/JSON | Order creation, tracking, inventory levels | API key | Yes | Pagination (250/page) | No |
| ShipStation REST API | HTTPS/JSON | Order import, label generation, shipment tracking | API key (Basic auth) | Yes | Pagination (500/page) | No |
| Flexport REST API | HTTPS/JSON | Freight shipments, tracking milestones, documents | API key (Bearer) | Yes | Pagination | No |
| EDI 940/945/856 | AS2, SFTP, VAN | Traditional WMS without API, large retailers | VAN certificate | No (batch) | Yes | Native |
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max records per page | 250 | ShipBob API | Use cursor-based pagination for full result sets |
| Max records per page | 500 | ShipStation API | Offset/limit pagination |
| Max request body size | 50 MB | Oracle WMS Cloud REST | Split larger XML payloads |
| Max IDoc batch size | 10,000 IDocs | SAP EWM (via PI/PO) | Larger batches cause qRFC queue congestion |
| Max concurrent REST calls | 5 per client | Manhattan Active WM | Queued beyond limit; 429 after 30s queue timeout |
| Limit Type | Value | Window | System |
|---|---|---|---|
| API calls | 40 requests/second | Per second | ShipStation (sustained) |
| API calls | Fair use / throttled | Rolling | Manhattan Active WM (per tenant) |
| Bulk file imports | 5 concurrent jobs | Per tenant | Oracle WMS Cloud |
| IDoc throughput | ~50,000/hour | Sustained | SAP EWM (depends on sizing) |
| Webhook deliveries | 1,000/minute | Per merchant | ShipBob |
| System | Flow | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| Manhattan Active WM | OAuth 2.0 client credentials | 1 hour | Yes (new token request) | Requires pre-registered client app |
| Blue Yonder WMS | MOCA session + API gateway token | Session-based (30 min idle) | Re-authenticate | Stateful — sticky sessions required |
| Oracle WMS Cloud | OAuth 2.0 | 1 hour | Yes | Uses Oracle IDCS |
| SAP EWM | SAP system user + RFC trust | Persistent | N/A | OData: OAuth 2.0 via BTP |
| ShipBob | API key (Bearer token) | Does not expire | N/A | One key per merchant channel |
| ShipStation | API key + secret (Basic auth) | Does not expire | N/A | Base64-encode key:secret |
| Flexport | API key (Bearer token) | Does not expire | N/A | Scoped per integration |
START — ERP-to-WMS/3PL Integration
|-- What WMS/3PL system?
| |-- Manhattan Active WM -> REST API (OAuth 2.0)
| |-- Blue Yonder WMS -> MOCA (legacy) or Integration API (modern)
| |-- Oracle WMS Cloud -> REST API (XML/JSON)
| |-- SAP EWM Embedded -> No external integration needed (shared DB)
| |-- SAP EWM Decentralized -> IDoc (batch) or OData (real-time)
| |-- 3PL (ShipBob/ShipStation/Flexport) -> REST API
| +-- Legacy/custom WMS -> EDI 940/945 via VAN
|-- What's the integration pattern?
| |-- Real-time order release (<1 min latency)
| | |-- < 500 orders/day -> Direct REST API calls, synchronous
| | |-- 500-5,000 orders/day -> Event-driven (middleware routes)
| | +-- > 5,000 orders/day -> Message queue + async workers
| |-- Batch order release (scheduled waves)
| | |-- EDI 940 via VAN -> Hourly/daily batch files
| | |-- File-based (CSV/XML) -> SFTP drop folder
| | +-- IDoc batch (SAP EWM) -> Scheduled ALE distribution
| +-- Hybrid (real-time order + batch inventory) -> Most common pattern
|-- Which data flows?
| |-- ERP -> WMS: Sales order, PO receipt, transfer orders, item master
| |-- WMS -> ERP: Ship confirm, ASN (856), inventory adjustments
| +-- Bidirectional: Inventory levels (real-time WMS, periodic ERP recon)
+-- Error tolerance?
|-- Zero-loss -> Idempotent APIs + DLQ + reconciliation job
+-- Best-effort -> Retry with exponential backoff
| Step | Source | Action | Target | Data Objects | EDI Equivalent | Failure Handling |
|---|---|---|---|---|---|---|
| 1 | ERP | Release sales order to WMS | WMS | Order header, lines, ship-to, priority | EDI 940 | Retry 3x, then DLQ + alert |
| 2 | WMS | Receive order, validate inventory | WMS (internal) | Allocation records | N/A | Reject to ERP with reason code |
| 3 | WMS | Wave planning + task assignment | WMS (internal) | Wave ID, pick tasks | N/A | Re-wave on failure |
| 4 | WMS | Pick, pack, generate shipping label | WMS + carrier | Carton contents, tracking #, weight | N/A | Manual intervention queue |
| 5 | WMS | Ship confirmation to ERP | ERP | Shipment ID, tracking #, shipped qty | EDI 945 | Retry 3x, then manual reconciliation |
| 6 | WMS/Carrier | ASN to customer/retailer | Customer | Carton details, PO ref, SSCC-18 | EDI 856 | Resend; chargebacks if late |
| 7 | ERP | Post goods issue, update inventory, invoice | ERP (internal) | Inventory movement, billing doc | EDI 810 | Reprocess from ship confirm |
| 8 | Both | Nightly inventory reconciliation | ERP + WMS | On-hand qty by location, lot, serial | EDI 846 | Variance report + cycle count |
Set up authenticated connections to both ERP and WMS systems. [src1, src3]
# Manhattan Active WM — OAuth 2.0 token request
curl -X POST https://{tenant}.manh.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&scope=wm-api"
# ShipBob — API key authentication
curl -X GET https://api.shipbob.com/1.0/order \
-H "Authorization: Bearer {SHIPBOB_API_KEY}" \
-H "shipbob_channel_id: {CHANNEL_ID}"
Verify: Successful HTTP 200 response with valid JSON body.
Before any order flow, ensure item master (SKU) data is synchronized between ERP and WMS. [src7]
{
"sku": "WIDGET-001",
"description": "Blue Widget 10-pack",
"upc": "012345678901",
"weight": 2.5,
"weight_uom": "LB",
"dimensions": { "length": 12, "width": 8, "height": 6, "uom": "IN" },
"lot_tracked": true,
"serial_tracked": false,
"hazmat": false,
"storage_class": "AMBIENT",
"erp_item_id": "MAT-100042"
}
Verify: Query WMS for the item by SKU — confirm all fields match ERP source.
Push sales orders from ERP to WMS for fulfillment. [src1, src4]
// Manhattan Active WM REST order release
{
"orderNumber": "SO-2026-00142",
"orderType": "SALES",
"priority": 2,
"requestedShipDate": "2026-03-05",
"shipTo": {
"name": "Jane Smith",
"address1": "123 Main St",
"city": "Austin",
"state": "TX",
"postalCode": "78701",
"country": "US"
},
"carrier": "FEDEX",
"serviceLevel": "GROUND",
"lines": [
{ "lineNumber": 1, "sku": "WIDGET-001", "quantity": 10, "uom": "EA" }
]
}
Verify: WMS returns HTTP 201 (REST) or functional acknowledgment 997 (EDI). Order appears in WMS work queue.
After pick/pack/ship, WMS sends shipment details back to ERP. [src4, src5]
{
"orderNumber": "SO-2026-00142",
"shipmentId": "SHP-88901",
"shippedDate": "2026-03-04T14:30:00Z",
"carrier": "FedEx",
"trackingNumber": "794644790132",
"lines": [
{ "lineNumber": 1, "sku": "WIDGET-001", "shippedQuantity": 10, "lotNumber": "LOT-2026-A1" }
],
"packages": [
{ "packageId": "PKG-001", "sscc18": "00100123456789012345", "trackingNumber": "794644790132" }
]
}
Verify: ERP shows shipment posted, inventory decremented, tracking stored on sales order.
Run nightly reconciliation between WMS and ERP to catch drift. [src7]
SELECT w.sku, w.location_id,
w.on_hand_qty AS wms_qty, e.on_hand_qty AS erp_qty,
(w.on_hand_qty - e.on_hand_qty) AS variance
FROM wms_inventory w
LEFT JOIN erp_inventory e ON w.sku = e.sku AND w.warehouse_id = e.warehouse_id
WHERE ABS(w.on_hand_qty - e.on_hand_qty) > 0
ORDER BY ABS(variance) DESC;
Verify: Variance report shows zero or known-exception rows only.
If shipping to retailers, generate ASN with SSCC-18 barcodes. Late or missing ASNs trigger chargebacks ($50-$500 per occurrence). [src4, src5]
; EDI 856 — Advance Ship Notice (simplified)
ST*856*0001~
BSN*00*SHP-88901*20260304*1430*0001~
HL*1**S~
TD5**2*FEDX*FG~
REF*CN*794644790132~
N1*ST*Jane Smith~
HL*2*1*O~
PRF*PO-2026-00142~
HL*3*2*P~
MAN*GM*00100123456789012345~
HL*4*3*I~
LIN*1*VN*WIDGET-001*UP*012345678901~
SN1*1*10*EA~
SE*14*0001~
Verify: Retailer EDI system returns 997 functional acknowledgment within SLA.
# Input: Sales order from ERP (order_number, items, ship_to)
# Output: ShipBob order ID and status
import requests # requests==2.31.0
SHIPBOB_API_KEY = "your_api_key"
CHANNEL_ID = "your_channel_id"
headers = {
"Authorization": f"Bearer {SHIPBOB_API_KEY}",
"Content-Type": "application/json",
"shipbob_channel_id": CHANNEL_ID
}
order_payload = {
"reference_id": "SO-2026-00142",
"order_number": "SO-2026-00142",
"type": "DTC",
"shipping_method": "Standard",
"recipient": {
"name": "Jane Smith",
"address": {
"address1": "123 Main St",
"city": "Austin", "state": "TX",
"zip_code": "78701", "country": "US"
}
},
"products": [{"reference_id": "WIDGET-001", "quantity": 10}]
}
resp = requests.post("https://api.shipbob.com/1.0/order",
json=order_payload, headers=headers)
resp.raise_for_status()
print(f"ShipBob Order ID: {resp.json()['id']}")
// Input: Date range for shipped orders
// Output: Array of shipment objects with tracking numbers
const axios = require('axios'); // [email protected]
const SHIPSTATION_KEY = 'your_api_key';
const SHIPSTATION_SECRET = 'your_api_secret';
const auth = Buffer.from(`${SHIPSTATION_KEY}:${SHIPSTATION_SECRET}`).toString('base64');
async function getShipments(shipDateStart, shipDateEnd) {
const shipments = [];
let page = 1, hasMore = true;
while (hasMore) {
const resp = await axios.get(
`https://ssapi.shipstation.com/shipments?shipDateStart=${shipDateStart}&shipDateEnd=${shipDateEnd}&pageSize=500&page=${page}`,
{ headers: { Authorization: `Basic ${auth}` } }
);
shipments.push(...resp.data.shipments);
hasMore = resp.data.pages > page;
page++;
}
return shipments.map(s => ({
orderNumber: s.orderNumber,
trackingNumber: s.trackingNumber,
carrier: s.carrierCode,
shippedDate: s.shipDate
}));
}
# Step 1: Get access token
TOKEN=$(curl -s -X POST https://{tenant}.manh.com/oauth/token \
-d "grant_type=client_credentials&client_id={ID}&client_secret={SECRET}" \
| jq -r '.access_token')
# Step 2: Query inventory for specific SKU
curl -s "https://{tenant}.manh.com/api/wm/inventory?sku=WIDGET-001" \
-H "Authorization: Bearer ${TOKEN}" | jq '.inventory[]'
| ERP Field | Manhattan | Blue Yonder | ShipBob | Type | Gotcha |
|---|---|---|---|---|---|
| Sales Order Number | orderNumber | ordnum | reference_id | String | ShipBob requires unique per channel |
| Ship-to Address Line 1 | shipTo.address1 | adrln1 | recipient.address.address1 | String | Blue Yonder: 40-char limit |
| Requested Ship Date | requestedShipDate (ISO 8601) | expdte (YYYYMMDD) | N/A (auto) | Date | Format varies per system |
| Item SKU | lines[].sku | ordlin.prtnum | products[].reference_id | String | Must match exactly — no fuzzy matching |
| Ordered Quantity | lines[].quantity | ordlin.ordqty | products[].quantity | Integer | Blue Yonder: base UOM only |
| Lot Number | lines[].lotNumber | ordlin.lotnum | N/A | String | ShipBob: FIFO only, no lot allocation |
| Carrier Code | carrier | carcod | shipping_method | String | Not standardized — maintain mapping table |
| Priority | priority (1-5) | pckpri (01-99) | N/A | Integer | Scale differs: Manhattan 1=highest |
| Unit of Measure | lines[].uom | ordlin.uom | N/A (always EA) | String | ShipBob: eaches only |
| Code | System | Meaning | Resolution |
|---|---|---|---|
| 429 | Manhattan / ShipBob | Rate limit exceeded | Exponential backoff: 2^n seconds, max 5 retries |
| 404 | Oracle WMS Cloud | Endpoint not found | Prepend version prefix (e.g., /v9/) to all API paths |
| 400 / INVALID_SKU | WMS (any) | Item not found in WMS | Sync item master before releasing orders |
| 422 | ShipBob | Unprocessable entity | Validate address and required fields pre-submission |
| IDOC_ERROR | SAP EWM | IDoc processing failure | Check WE02/WE05; fix mapping in WE20 |
| 997-R | EDI (any) | Functional acknowledgment rejected | Review 997 AK5 segment for specific error codes |
Automated item master sync before every order release wave. [src7]Configure ERP to accept partial shipments; create backorder lines. [src1]Trigger 856 on carrier manifest close, not batch schedule. [src4]Use event-driven inventory for ATP; batch recon for audit only. [src7]Maintain carrier crosswalk table in middleware. [src1]Poll as fallback every 15 min; deduplicate by shipment ID. [src6]# BAD — polling creates load and still has up to 60s latency
while True:
orders = wms_api.get_orders(status="shipped")
for order in orders:
erp.update_shipment(order)
time.sleep(60)
# GOOD — WMS pushes ship confirmations as they happen
@app.route('/webhook/wms/shipment', methods=['POST'])
def handle_shipment_webhook():
payload = request.json
if db.shipment_processed(payload['shipmentId']):
return jsonify({"status": "duplicate"}), 200
erp.post_goods_issue(payload)
db.mark_shipment_processed(payload['shipmentId'])
return jsonify({"status": "ok"}), 200
// BAD — orders with unknown SKUs silently fail
async function releaseOrder(order) {
await wmsApi.post('/orders', order);
}
// GOOD — catch missing items before fulfillment delays
async function releaseOrder(order) {
const wmsCatalog = await wmsApi.get('/items?skus=' + order.lines.map(l => l.sku).join(','));
const wmsSkus = new Set(wmsCatalog.data.map(i => i.sku));
const missing = order.lines.filter(l => !wmsSkus.has(l.sku));
if (missing.length > 0) {
await syncItemMaster(missing.map(l => l.sku));
}
await wmsApi.post('/orders', order);
}
# BAD — eventual consistency gaps cause silent inventory drift
def get_available_inventory(sku):
return wms_api.get_inventory(sku)['available']
# GOOD — use WMS for real-time ATP, reconcile nightly
def reconcile_inventory():
wms_snapshot = wms_api.get_full_inventory_snapshot()
erp_snapshot = erp_api.get_inventory_by_warehouse()
variances = find_variances(wms_snapshot, erp_snapshot)
if variances:
create_cycle_count_request(variances)
Sync in order: warehouses, items, addresses, then orders. [src7]Maintain UOM conversion table in middleware with mandatory validation. [src1]Normalize all dates to UTC in transit; convert to local at display. [src7]Micro-batch on order event or switch to real-time API. [src4]Throttled release at warehouse pick rate (e.g., 500/hour). [src1]Load-test with 2x expected peak volume before go-live. [src7]# Check ShipBob API connectivity
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer {API_KEY}" \
-H "shipbob_channel_id: {CHANNEL_ID}" \
https://api.shipbob.com/1.0/product?limit=1
# Check ShipStation API connectivity
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Basic {BASE64_KEY_SECRET}" \
https://ssapi.shipstation.com/orders?pageSize=1
# Test Manhattan Active WM OAuth token
curl -s -X POST https://{tenant}.manh.com/oauth/token \
-d "grant_type=client_credentials&client_id={ID}&client_secret={SECRET}" \
| jq '{token: .access_token[:20], expires: .expires_in}'
# SAP EWM — check IDoc status
# Transaction WE02: filter by message type WMMBXY
# Transaction SM58: check tRFC/qRFC queue for stuck IDocs
# EDI 940 transmission verification
# SPS Commerce portal -> Transactions -> filter Document Type 940
# Check 997 status: Accepted (A) vs Rejected (R)
| System | Current Version | Previous Major | Breaking Changes | Notes |
|---|---|---|---|---|
| Manhattan Active WM | Cloud-native (versionless) | Pre-2023 (on-prem) | REST replaced SOAP | On-prem to cloud = full API rewrite |
| Blue Yonder WMS | 2024.x | 2022.x (JDA) | Integration API replaces some MOCA | MOCA still needed for advanced ops |
| Oracle WMS Cloud | 24B (REST v9+) | 21C | JSON support added (was XML-only) | Version prefix required in URIs |
| SAP EWM | S/4HANA 2023+ | ECC 6.0 EWM | Embedded removes RFC need | Decentralized still uses IDoc/RFC |
| ShipBob API | v1.0 | N/A | Webhook engine added 2025 | No breaking changes to date |
| ShipStation API | v3 | v2 | Pagination + rate limits changed | v2 deprecated |
| Flexport API | 2025-03 | 2023-10 | Products/Parcels APIs added | Version in URL path |
| EDI 940/945/856 | X12 008010 | X12 005010 | Minimal — backward compatible | Most partners still on 005010 |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Dedicated WMS separate from ERP | ERP built-in warehouse module meets needs | ERP-native warehouse configuration |
| Outsourcing fulfillment to 3PL | Own warehouse with ERP-native WMS | ERP-native warehouse module setup |
| Retailers requiring EDI 856 ASN | Direct-to-consumer only, no retailer requirements | Simple shipping label integration |
| 500+ orders/day or complex operations | Fewer than 100 orders/day, simple pick-pack-ship | Manual fulfillment or ERP-native shipping |
| Real-time visibility across multiple warehouses | Single warehouse, single system | ERP inventory management |
| Lot/serial tracking or hazmat compliance | Simple consumer goods, no tracking | Basic 3PL integration without lot/serial |
| Capability | Manhattan Active WM | Blue Yonder WMS | Oracle WMS Cloud | SAP EWM | ShipBob (3PL) | ShipStation (3PL) |
|---|---|---|---|---|---|---|
| API Style | REST (JSON) | MOCA + REST | REST (XML/JSON) | OData + IDoc + RFC | REST (JSON) | REST (JSON) |
| Auth Model | OAuth 2.0 | MOCA session + token | OAuth 2.0 (IDCS) | SAP system + OAuth | API key (Bearer) | API key (Basic) |
| Real-time Orders | Yes (REST) | Yes (MOCA/API) | Yes (REST) | Yes (OData/qRFC) | Yes (REST) | Yes (REST) |
| Batch Orders | Via middleware | Yes (file/EDI) | Yes (file import) | Yes (IDoc batch) | No | Yes (CSV import) |
| EDI 940/945 Native | No | Yes (via SPS) | Yes (via Oracle IC) | Yes (via PI/PO) | No | No |
| Webhook Support | Yes | Limited | Limited | No | Yes (dashboard) | Yes |
| Inventory Query API | Yes | Yes (MOCA) | Yes | Yes (OData) | Yes (per SKU) | No |
| Lot/Serial Tracking | Full | Full | Full | Full | No | No |
| Multi-warehouse | Yes | Yes | Yes | Yes | Yes (network) | No |
| Deployment Complexity | Low (cloud) | High (on-prem/hybrid) | Medium (cloud) | High (S/4HANA) | Low (SaaS) | Low (SaaS) |