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 today — https://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 The response stores the parsed PO in the document ledger under reference
po-4500123987.edi: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
Your consumer of that event creates the commerce order. Key the
edi.purchase_order.received for the StateSet sequencer, and a worker flushes the outbox every
EDI_FLUSH_SECONDS (or you flush it now):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 — Using the PO number as A larger PO stops at
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.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 500 autonomy cap and
will not park at the review gate; poll status until it lands: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 Then send the ASN through EDI, cartons and SSCCs matching the physical labels:The body that comes back is the wire-ready X12 856 (
lines ships every remaining unit: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 Keep the 810’s
overdue is computed from the send: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
(A short-pay (the retailer deducts a chargeback) is applied at the amount actually received; the
balance ages at its true age, and
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: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.