An 856 is an aggregation tree wearing a trench coat.
Strip the envelope off an X12 856 and what remains is a tree. The despatch advice describes a shipment that contains orders, orders that contain packs, packs that contain items — a rooted hierarchy the message spells out one node at a time, each node naming its parent. That is also, exactly, the shape of the EPCIS aggregation model: an SSCC parent, its children, and the events that assert containment. The mapping between the two is not a heuristic. It is deterministic, and this post walks it on a worked pair.
The tree, in original prose
The 856 builds its hierarchy with a repeating structural segment: each occurrence carries its own sequence number, a pointer to its parent's number, and a level code — shipment, order, tare, pack, or item. Reading the segments in order and following the parent pointers reconstructs the tree; nothing about the nesting is implicit. Pack-level nodes carry the marks physically applied to the freight — in GS1 practice, the SSCC of the pallet or case. Item-level nodes carry the trade item and its quantity: the GTIN, and where serialization applies, the serials; where lot-tracking applies, the lot.
That is all the structure this mapping needs, stated in our own words. For element-by-element segment detail, use Stedi's X12 856 reference — the canonical public reference, credited here and linked, never copied. (Why we link instead of republishing is its own post.)
A self-authored fixture
This wire document is synthetic — authored for this corpus, no partner's implementation guide reproduced:
ST*856*0001~
BSN*00*5501*20260729*0712~
HL*1**S~
DTM*011*20260729~
REF*BM*80144100~
N1*SF*ACME FOODS*UL*0614141007776~
N1*ST*NORTHDOCK DC*UL*0614142008888~
HL*2*1*O~
PRF*PO-06012-1234~
HL*3*2*P~
MAN*GM*006141410000055012~
HL*4*3*I~
LIN**UP*614141073467*LT*7A-4412~
SN1**96*EA~
CTT*4~
SE*16*0001~
One shipment, one order, one pallet, one lot-tracked line: 96 each of GTIN 614141073467, lot 7A-4412, on SSCC 006141410000055012, shipped 2026-07-29 against PO-06012-1234, ship-from and ship-to identified by GLN.
The mapping table
| ASN fact | source | EPCIS landing zone |
|---|---|---|
| shipment node | root of the hierarchy | shipping ObjectEvent, bizStep: shipping, disposition: in_transit |
| pack node + mark | pack level, SSCC in the marks segment | AggregationEvent, parentID = SSCC EPC URI |
| item node, serialized | item level, GTIN + serials | childEPCs as SGTIN URIs |
| item node, lot-grain | item level, GTIN + lot + quantity | childQuantityList as LGTIN + quantity + UoM |
| ASN number | BSN02 | bizTransactionList: { "type": "desadv", … } |
| PO number | PRF | bizTransactionList: { "type": "po", … } |
| bill of lading | REF (BM) | bizTransactionList: { "type": "bol", … } |
| ship-from / ship-to GLNs | party loop, GLN qualifier | sourceList / destinationList (owning_party, location) |
| despatch date | DTM (011) | eventTime of the shipping event |
Every row lands on a core field. No extensions, no invented vocabulary — this is the machinery CBV built for exactly this document set.
The output, validated
biztx join emits the event skeleton; here is the expected-output fixture for the document above, from the corpus pair. The aggregation event:
{
"type": "AggregationEvent",
"parentID": "urn:epc:id:sscc:0614141.0000005501",
"childQuantityList": [
{ "epcClass": "urn:epc:class:lgtin:0614141.007346.7A-4412",
"quantity": 96, "uom": "EA" }
],
"action": "ADD",
"bizStep": "packing",
"bizTransactionList": [
{ "type": "desadv",
"bizTransaction": "urn:epc:id:gdti:0614141.11711.5501" }
]
}
and the shipping event carries the SSCC in epcList, all three document references, and both parties. The whole pair validates against the pinned official schema:
$ npx epcis.dev validate 856-expected.epcis.json
valid schema=epcis-json-schema.json version=2.0.1
$ npx epcis.dev hash 856-expected.epcis.json
hash 0 ni:///sha-256;a10cc2beddbd2cc3757d1efd00e9a5359cb9734440678d946dd6974e69a7da74?ver=CBV2.0
hash 1 ni:///sha-256;2ae830781eb07477910e75430daea6c1bea8fd04ad44bd1ac168e8b1daa8d8b0?ver=CBV2.0
Validation is the acceptance gate: an event the pinned EPCIS 2.0.1 schema rejects never leaves the join. And because identity is the CBV §8.9 content hash, the same ASN mapped twice produces the same events — replays cost nothing.
What the mapping refuses to invent
A deterministic mapping is honest about what the document does not know. An 856 asserts what the shipper says was packed and shipped — so the join emits packing and shipping events with the despatch date the document itself carries, and it stops there. It does not fabricate a receiving event, a commissioning time, or a business step the document never witnessed. Missing quantities, an orphaned parent pointer, a cycle in the hierarchy: each is a typed rejection naming the node, never a silently pruned tree. The events that say what actually arrived come from your scanners — and diffing the two is the entire receiving story.
The EDIFACT twin of this mapping — DESADV, at full open-content depth — is here, CPS packing tree and all.
Validate the fixture pair now with npx epcis.dev validate — no account, exit code is the verdict. Run your own ASNs through the join: the document verbs ride the capture surface — get a capture key.
We answer in writing. We take at most five conversations a month, only when you ask for one, and only after you already have the written read.