Back to all posts
Developers & Integrations

Wiring Line-Level Order Routing Into Your OMS/ERP: The Integration Data Contract for a China Fulfillment Partner

By Alice Zhou2026-09-129 min read
WooliiPorterWiring Line-Level Order Routing Into Your OMS/ERP: The Integration Data Contract for a China Fulfillment Partner

Short answer first: line-level order routing is not a toggle in a dashboard — it is a data contract. The moment one order can contain both stock lines (received, evidence-captured and consolidated at our China warehouse) and supplier-direct lines, your OMS or ERP has to treat each order line as an independently routed object with its own state, its own tracking and its own exception path. This article covers the implementation layer: which objects and fields to define, how to design event semantics so you are not undone by duplicate dispatches and out-of-order states, and what can honestly be integrated today.

If the business decision itself is still open, read stock fulfillment vs supplier-direct shipping and line-level routing for mixed orders first. If the direction is settled but you are unsure what data to clean up, see the master-data types to prepare before integration. For the field list behind automation exchanges, see fulfillment data in the MCP/API era. This article assumes the decision is made and you are holding a schema diagram.

Why per-line has to be a property of the data model, not a column in the UI

One order, one example: six units of a SKU already stocked at our China warehouse, two units of another SKU that can only ship from your supplier. Same order number. Two fulfillment models. Two tracking numbers. Two different owners of the exception path. If your order table carries a single shipping-status field, you cannot express “line 1 has left the China hub while line 2 is still waiting on supplier pickup.” You end up encoding that into free-text notes, and every downstream consumer — support, reconciliation, your own dashboard — has to guess.

The consequences are concrete: support cannot answer “where is my order” without a human, partial refunds get attributed to the wrong lines, and reconciliation between your system and your fulfillment partner drifts quietly until peak season exposes it.

So routing belongs on the line: each line carries a routing_mode (stock fulfillment or supplier-direct), set when the order enters your system and changeable only through an auditable change event.

The integration data contract: objects to define before you write code

These are the objects we walk through with integration partners. Field names are yours; the semantics are not optional.

ObjectKey fieldsOwnerWhy it matters
SKU ↔ supplier ↔ routing mapsku, supplier_id, routing_mode, consolidation eligibility, battery/hazmat flags, source of dims and weightMerchant (we validate)Turns routing into a lookup instead of a judgment call at packing time
Order lineorder_id, line_id (stable), sku, qty ordered, qty dispatched, routing_mode, split sequence, statusMerchant OMSA stable line_id is what survives edits, splits and cancellations
Parcel / shipmentshipment_id, linked line_ids, mode, measured weight, dims, sealed_at, dispatched_atFulfillment partnerTracking attaches here, not to the order
Evidence recordevidence_id, parcel_id, type (photo / count / weight), captured_at, operator referenceFulfillment partnerMakes the evidence chain readable by your system, not only by a human
Tracking recordshipment_id, carrier, tracking_number, tracking_url, first scan time, statusCarrier via partnerWritten back per parcel, aggregated per line at read time
Exception eventexception_id, linked line_id or parcel_id, type, detected_at, required ownerSharedShort-ship, damage, unreachable address, customs hold, supplier delay all get an owner

Two of these deserve emphasis. First, the SKU-to-supplier mapping is merchant-maintained: you know which supplier makes which SKU and which SKUs you want stocked. Our side validates that mapping against parcels we actually receive and flags conflicts — one SKU mapped to two suppliers, missing SKU, quantity mismatch. Second, declare where dims and weight come from: your data or our measurement at receiving. Mixing the two makes reconciliation impossible.

State machines and tracking write-back: do not force both paths into one status set

Fulfillment modelState sequenceTerminal states
Stock fulfillmentreceived → evidence captured → pending consolidation → consolidated → dispatched → in transit → delivereddelivered, cancelled, returned
Supplier-directinstruction sent → supplier confirmed → picked up → in transit → delivereddelivered, cancelled, failed

Every transition should carry a timestamp, the triggering party (warehouse scan, merchant API, carrier) and the source system. Do not let your system infer state from a tracking number alone: tracking tells you what transport did, not whether a parcel was received, evidence-captured or still waiting to be consolidated.

The tracking rule is simple: one order can produce N parcels and N tracking numbers. Store tracking at parcel level, aggregate to line level when you read. Never overwrite an order-level field with it.

On weight: consolidation can change the volumetric weight of a shipment and therefore can change how it is billed, but that is not a guarantee of lower shipping cost — it depends on what is in the parcel and on the carrier’s billing rules. The mechanics are covered in volumetric weight vs chargeable weight.

Exception events should at minimum cover: short-ship at receiving (received quantity below expected), damage at receiving, unreachable address, customs or documentation hold, and supplier failure to hand over on time. Each one needs a field for who acts next, or it just becomes another email thread.

Events, idempotency, and the tests that find the bugs

  • Event envelope: every event carries event_id, event_type, occurred_at (UTC), entity references, state_version and payload. Consumers deduplicate on event_id and apply by state_version, so a late-arriving event cannot roll state backwards.
  • Commands need idempotency keys: creating a receiving instruction, editing a line, cancelling a line, changing an address, requesting dispatch. A retried command must not create a second parcel or a second dispatch.
  • Partial quantities must be expressible: track dispatched quantity per line; the line stays open until dispatched quantity equals the ordered quantity or the remainder is explicitly cancelled. Cancelling after dispatch is not a cancellation — it is a return.
  • The evidence chain should be machine-readable: photos, counts and weight readings become structured records with timestamps and parcel references, so your system can render what was verified, when, and with what result.

Test these before go-live; they are exactly where integrations break:

  1. A mixed order whose line quantity is edited after receiving;
  2. Cancelling one of two lines;
  3. Splitting one line into two parcels;
  4. A short-ship at receiving;
  5. An address change after receiving but before dispatch;
  6. The same event delivered twice;
  7. Events arriving out of order (dispatch before evidence capture);
  8. Delivered, but tracking not yet written back;
  9. A supplier-direct line with no carrier scan for a long period.

Also write down what “accepted” means: which events, which fields, which exception types, which test cases passing. Settling that during a small pilot is far cheaper than settling it before peak season.

Where integration actually stands today

So that automation promises do not run ahead of capability, here is the honest scope. Integration interfaces are currently handled as design-partner / private-pilot work. We do not promise publicly available API keys, and we do not promise production webhooks or a marketplace app for general use. Channel status is stated only as verified: our WooCommerce plugin currently has a listing in the WordPress.org directory — a directory listing, which is not the same as a listing in the WooCommerce Marketplace. For Shopify and Amazon FBA we state only the progress we have verified, and we will not describe a channel as generally available before a first real order has been delivered through it.

The practical path is: submit a workflow review at /request-workflow-review → we review order intake, routing rules, exception handling and data ownership together → then field mapping → then a scoped pilot exchange → then acceptance against agreed test cases. That sequencing is deliberate. Routing projects usually fail on the data contract, not on the interface itself.

FAQ

Can we integrate without an app in any marketplace?

Yes. Pilot integrations are defined per partner: agreed field mapping, agreed event set, agreed test cases. None of that requires a published app.

Who maintains the SKU mapping?

You do. You know which supplier makes which SKU and which SKUs you want stocked. We validate the mapping against parcels we actually receive and flag conflicts. Goods remain your property throughout; we do not purchase from your suppliers and we never handle payment to them.

What data actually moves in an MCP/API integration?

Inbound and receiving status, evidence events (photo, count, weight), consolidation events, outbound dispatch and tracking write-back, and exception events with a required-owner field. Not payment information, and not your supplier purchase orders.

How long does acceptance take?

We do not publish fixed timelines, because scope drives it. The sequence is review → mapping → test cases → acceptance, and the definition of done is agreed in writing before work starts.

Can one order run on both models?

Yes — that is the case this contract exists for. Per-line quantity, state and tracking is the premise of the design.

What happens if a line cannot ship as planned?

It becomes an exception event with an owner and a next action, rather than a status that changes quietly.

If you want to map this against your own schema, start at /developers and submit a workflow review at /request-workflow-review. If you are still evaluating fulfillment models, /for-merchants and /how-it-works cover the operational side, and /integrations lists channel status exactly as verified.