Aug 25, 2026
B2B EDI Exchange (Order-to-Cash)
How it works
The obvious places to look in an order-to-cash EDI flow are the two ends: did the partner's 850 arrive, and did a valid 856 go back out. Those are the cheapest things to verify and the least likely to be where the flow actually breaks. The real complexity sits in the middle band — the translator and the 997 acknowledgment step — because that is the only point where the flow changes its own semantics. Upstream of the translator, an 850 is a file; the AS2/SFTP gateway cares about transport, envelopes, and delivery, not whether the purchase order means anything. Downstream of the Order Management System, the record is an order with internal identity, and fulfillment and the ASN generator work from that identity. The translator is where a partner-shaped document becomes a system-shaped order, and the 997 is where the flow commits to a claim about that transformation and sends it back to the partner. A 997 is not a delivery receipt the gateway could have produced; it asserts that validation happened. That makes it the flow's only outbound promise about internal state, and promises are where defects become other people's problems.
So a QA engineer's first attention belongs to the coupling between validation and acknowledgment, not to the endpoints. The failure modes worth designing tests around are the disagreements: a document the translator accepts but the Order Management System cannot instantiate as an order, or a 997 that reports acceptance for a payload that never produced a fulfillable order at all. In the first case the partner has been told their order is good and will wait for goods; in the second, the flow has silently dropped an order while looking healthy from both ends — the gateway logged a successful transfer, a 997 went out, and no 856 will ever follow because there is nothing in fulfillment to trigger it. Notice that the absence of an ASN is the only externally visible symptom, and it appears far downstream and far later than the defect. That asymmetry is the argument: testing the 850 arrival and the 856 content confirms the flow works when it works, while testing translator-to-OMS agreement and 997 truthfulness is what tells you what the partner has been promised. Verify that no 997 asserts acceptance for a document the Order Management System has not actually accepted, and that every accepted order reaches the shipment trigger, because that is the seam the endpoints cannot see.
Caveats — what breaks in practice
The failure mode most likely to blindside a team is the record that is accepted but sits in a staged state forever. Everything about it looks like success at the moment of the exchange: the target returned an acceptance, no auth expiry fired, no oversized payload was rejected, no poison message halted the queue, no retry storm lit up a dashboard. The other candidates on this list announce themselves — throttling that silently drops messages at least shows up as a count mismatch, a batch split that loses or duplicates line items shows up in reconciliation, a job failure that leaves records permanently unposted shows up as an error state you can query. Staged-forever produces no error, no exception, and no delta anyone is looking for, because staged is a legitimate intermediate state that most records pass through in milliseconds.
The assumption that makes it surprising is that acceptance implies eventual completion — that the transition from staged to posted is guaranteed by the target once it has taken ownership of the record. That assumption is reasonable and usually true, which is exactly why it isn't tested. The same system already exhibits an eventual-consistency gap where a record is staged but the effect is delayed past SLA, so teams learn to tolerate lag in the staged state; that tolerance is the cover under which permanent staging hides. For QE, this means the acceptance response is not a valid assertion target. The test has to close the loop on the terminal state, with a bound on how long staged is allowed to persist, and treat any record still staged past that bound as a failure rather than as latency — otherwise the only detection mechanism is a downstream party noticing that an order never became cash.
How to test this end to end
Picture a Friday-night drop from a big-box retailer: interchange ISA13 `000004871` carrying a single EDI 850 purchase order, PO number `4500219883`, ship-to DC `0742`, with 48 line items each keyed by a UPC in PO1-07 and a `UOM` of `CA`. It lands on the AS2 gateway as `PO_4500219883_20240614T2358.edi`, gets picked up by the EDI translator, and is mapped into the internal order object — `orderRef: "4500219883"`, `partnerId: "TGT-0742"`, `lines[48]`. Validation runs, and the checkpoint's output is an EDI 997 functional acknowledgment referencing group control number `GS06=4871` with `AK5=A`, written back to the partner's outbound directory. That 997 is the artifact the partner will point at weeks later if they claim the order was never received, and the internal order object is what eventually feeds the fulfillment trigger that fans out into the ASN and the invoice.
The nasty version of this is a partial mapping failure that still produces a positive acknowledgment. Suppose line 37 of `4500219883` arrives with an empty PO1-04 unit price and the mapper coerces it to null rather than rejecting it; validation passes on envelope structure, `AK5=A` goes out, and the partner now has proof of acceptance for a 48-line order while the internal object carries 47 usable lines — or, if the batch was split for size, 48 lines with one silently duplicated. The divergence surfaces much later and much more expensively, because the ASN and invoice are generated independently and off different schedules, so a bad line can ship correctly and bill wrong, or vice versa, with no single document to blame. To test it, replay `PO_4500219883_20240614T2358.edi` with line 37's price blanked and assert three things together: the internal order object has exactly 48 lines with matching UPCs and no duplicates, the 997 for `GS06=4871` reports `AK5=E` with an AK3/AK4 segment naming the offending element rather than a blanket accept, and no fulfillment trigger fires for `4500219883`. Then re-run the same file forty times concurrently under the partner's peak-window rate to confirm the gateway either acknowledges or rejects each interchange explicitly — a missing 997 for any `ISA13` is the failure, not just a wrong one.
Carry `4500219883` one hop further: the internal order object — `orderRef: "4500219883"`, `partnerId: "TGT-0742"`, `lines[48]` — is handed to the Order Management System, and CP2 is the moment OMS actually owns it. The order lands in OMS staging as `orderRef 4500219883` with a status of `STAGED`, awaiting the promotion that makes it a live order and arms the fulfillment trigger. Because the 997 with `AK5=A` and `GS06=4871` has already gone back to the partner's outbound directory, the trading partner now believes this order exists in full; from their side, CP2 is invisible. Whatever OMS does — or fails to do — with those 48 `CA`-unit lines against ship-to DC `0742` is no longer covered by any acknowledgment the partner will ever see.
Three things bite here. The order can sit in `STAGED` indefinitely: nothing in the acknowledgment chain says "promoted," so a stalled `4500219883` looks identical to an accepted one until the DC calls asking where Friday's drop went. Target-side defaults can quietly rewrite values — OMS filling a missing unit price on line 37 with a catalog default, or normalizing `UOM: CA` to `EA` and silently multiplying quantities, which then flows straight into the independently-generated ASN and invoice and guarantees they disagree. And upstream redelivery of `PO_4500219883_20240614T2358.edi` — an AS2 retry after a slow 997 write — can produce two staged copies of `orderRef 4500219883`, both eligible for promotion, both capable of firing the fulfillment trigger. To test, deliver `4500219883` to OMS and assert the staged record leaves `STAGED` within the promotion SLA and that a stuck record raises an alert rather than just aging; assert all 48 lines retain `UOM: CA` and the exact PO1-04 values from the source file, with any OMS-applied default surfaced as an explicit field-level flag instead of a substituted number; then redeliver the identical file and assert OMS holds exactly one order for `orderRef 4500219883`, keyed on partner plus PO number, and that exactly one fulfillment trigger fires so the ASN and invoice for DC `0742` are each generated once.
Once `4500219883` is promoted out of `STAGED`, the fulfillment trigger fires and CP3 is where that single event fans into the ASN path: the trigger hands the ASN Generator a shipment context — say `shipmentId: "SHP-0742-88117"`, `orderRef: "4500219883"`, `shipToDC: "0742"`, and the 48 shipped lines — which becomes an EDI 856 destined for TGT-0742's inbound directory. The interesting detail is that the ASN is generated independently of the invoice off that same fork, so the 856 for `SHP-0742-88117` carries its own copy of quantities and UOM. If the generator posts only part of the batch — a timeout after line 31 of 48 — the 856 goes out claiming a 31-line shipment against a 48-line order, and the partner has no acknowledgment mechanism that would flag the missing 17; they'll just receive an 856 that under-reports, then later an 810 built from the full 48 and the two documents disagree. Worse variants: the generator job dies outright and `SHP-0742-88117` never produces an 856 at all (the truck arrives before the notice), or a session expiry on the outbound transport triggers retry storms that push three copies of the same 856 for `shipmentId SHP-0742-88117`, each of which the partner's receiving system may treat as a distinct inbound shipment against DC `0742`.
To test, fire the fulfillment trigger for `4500219883` and assert exactly one 856 is generated for `shipmentId SHP-0742-88117`, containing all 48 lines with `UOM: CA` and quantities matching the promoted OMS lines — not the pre-promotion staged copy — and that the 856 line count and the later 810 line count for the same order are asserted equal in a single reconciliation check. Then inject faults at the generator: kill the job mid-batch after line 31 and assert the partial 856 is never transmitted (the shipment stays in an unposted-but-alerting state rather than shipping a truncated document); expire the outbound session and assert retries are bounded and idempotent so `SHP-0742-88117` yields one transmission, not three; and finally assert that a fulfillment trigger which produces no 856 within the ASN SLA raises an alert against `orderRef 4500219883` instead of silently aging, since the partner's 997 for the 850 tells them nothing about whether their shipment notice was ever built.
CP1 — Event Routing & Execution
Trading Partner Order Submission (EDI 850) → AS2/SFTP Gateway → EDI Translator → Validation & Acknowledgment (EDI 997)
CP2 — Target Delivery
Order Management System
CP3 — Target Posting & Application
Fulfillment / Shipment Trigger → ASN Generator (EDI 856)
Want this level of breakdown for your own system? Match your architecture in a few questions — no confidential upload required.