Skip to main content
One purchase order, four engines. A retailer, acme-retail, sends an X12 850 for twelve SHOE-RED-10 under PO 4500123987. The EDI engine translates it and emits an event; that event becomes an order in the Commerce Engine; fulfillment runs as a durable Temporal workflow that dispatches to the 3PL; the 856 ASN and 810 invoice go back out through EDI; and the loop closes twice — operationally in EDI reconciliation, financially in the Commerce Engine’s accounts receivable. Solid arrows below are API calls this guide makes. Dashed arrows are the seams — events and glue code you own, called out honestly where they occur.
Each engine has its own host and key. EDI: https://edi.stateset.com, Authorization: Bearer. Temporal: https://api.workstream.stateset.com, x-api-key. Sync (behind the workflow’s dispatch): https://api.sync.stateset.com, x-stateset-api-key. The Commerce Engine is embedded and has no public host todayhttps://api.stateset.com/api/v1 is the placeholder base URL the Commerce API reference uses; substitute the host where your engine runs. The calls are shown against the placeholder so they match the reference.
1

The 850 arrives at the EDI engine

The retailer’s interchange (adapted from the first 850 guide — same partner setup, new PO) is posted to the inbound door, which auto-detects the syntax and routes by ISA sender. Save it as po-4500123987.edi:
The response stores the parsed PO in the document ledger under reference 4500123987, returns the 997 as ack_997 (delivering it to the retailer over HTTP is your job), and reports events_queued: 1: an edi.purchase_order.received event is now sitting in the durable outbox. A resend of the same interchange is a 409 Conflict — the gateway dedups on (partner, interchange control number), so the retailer’s retries cannot create a second PO.
2

Cross the first seam: event out, order in

This seam is not an API call. The EDI engine does not call the Commerce Engine; it queues edi.purchase_order.received for the StateSet sequencer, and a worker flushes the outbox every EDI_FLUSH_SECONDS (or you flush it now):
Your consumer of that event creates the commerce order. Key the Idempotency-Key to the PO number, so a replayed event collapses into the same order:
customer_id is the Commerce Engine record for acme-retail — the mapping from ISA sender to customer UUID is yours to keep. The default stock_policy reserves what is available and backorders the rest; pass reject_if_insufficient if a wholesale PO you cannot fill should fail loudly instead. With the order accepted, answer the retailer with an 855 (POST /v1/edi/outbound/855/acme-retail) exactly as the first 850 guide does — line-level IA/IB statuses belong there.
3

Fulfill it as a durable Temporal workflow

Two engines could plausibly own this hop. The Sync Server’s own endpoints are per-tenant sync triggers and status writes — POST /v1/tenants/{tenant_id}/sync/dcl triggers a DCL order synchronization, and POST /v1/tenants/{tenant_id}/orders/{order_id}/fulfillment records a fulfillment status — useful, but not a durable orchestration. The Temporal Engine’s order-fulfillment workflow is: it reserves inventory, passes an NSR gate, and dispatches to your 3PL through a sync-server tool (fulfillment_tool), with 8 retry attempts per activity, deterministic idempotency keys, and replay-safe state. So fulfillment runs on Temporal, and the Sync Server sits behind the workflow’s dispatch activity rather than being called directly.
Using the PO number as order_id makes the workflow id deterministic per PO — order-fulfillment-{brand_uuid}-4500123987 — so a duplicate start returns the existing run instead of shipping twice. At 294.00thisorderisunderthedefault294.00 this order is under the default 500 autonomy cap and will not park at the review gate; poll status until it lands:
A larger PO stops at running:review_gate and waits up to 7 days for POST …/$WF/review — the durable workflow guide walks that gate, the SSE event stream, and what cancellation compensates.
4

Mark it shipped, send the 856

The workflow’s terminal result carries the fulfillment payload from the 3PL — carton, SSCC and tracking data. Turning that result into the next two calls is your glue code (the second seam: the Temporal Engine does not call the Commerce or EDI engines for you). First record the shipment on the commerce order — omitting lines ships every remaining unit:
Then send the ASN through EDI, cartons and SSCCs matching the physical labels:
The body that comes back is the wire-ready X12 856 (Content-Type: application/edi-x12), recorded as pending in acknowledgment reconciliation until the retailer’s 997 names it. Send it when the truck leaves — the ASN is the document retailers charge back on most.
5

Invoice it twice: the 810 for the retailer, the AR invoice for your books

The 810 is what the retailer pays against. Twelve × 24.50 with no allowances is 294.00 — the gateway does not compute the total, and a total that does not foot is the classic short-pay trigger:
The X12 document bills the retailer; it does not touch your ledger. Mirror it in the Commerce Engine so AR has something to age — create the invoice against the order, then send it, because overdue is computed from the send:
Keep the 810’s invoice_number in the commerce invoice’s notes (as above): when the remittance references the X12 number, you want one search to find the AR record.
6

Close both loops: 997 reconciliation, then cash

Operationally, the 856 and 810 stay pending until the retailer’s 997s arrive through the same inbound door as their 850. Watch the outstanding set shrink, and read the whole journey from the PO’s point of view:
Financially, the loop closes when the retailer’s payment is applied. Record the payment (POST /api/v1/payments, then /{id}/complete — the order-to-cash guide covers it) and connect it to the invoice; until you do, the invoice stays open and AR aging shows a delinquent retailer who already paid:
A short-pay (the retailer deducts a chargeback) is applied at the amount actually received; the balance ages at its true age, and GET /v1/evidence/4500123987 on the EDI side packages the document chain when the deduction needs disputing.

What you built

The seams between engines — 850 event to order create, workflow result to ship/856 — are yours: events and glue code, deliberately idempotent at every hop, never a hidden API call.

Next steps

The full EDI cycle

Partner onboarding, the 855, validation, TA1s, and every place an EDI error surfaces.

Durability, tested

The review gate, the SSE event stream, retries, cancellation and compensation on the same workflow.

The sync server at work

The engine behind fulfillment_tool — syncing orders, inventory and fulfillments between systems.

Commerce API reference

All 432 operations of the embedded engine, orders through the general ledger.
Last modified on August 31, 2026