Your ASN was always EPCIS vocabulary.
Every supply chain keeps two records of the same physical reality: the documents the business trades on — the purchase order, the despatch advice, the invoice, the receiving advice — and the events the goods generate as they move. Most architectures treat those as separate worlds and budget an integration project to glue them together. The uncomfortable fact for that budget line: GS1 already did the glue, in the standard, and almost nobody ships it.
The spec did this on purpose
CBV 2.0 §7.3 defines the business-transaction type vocabulary. It is a closed list of fourteen values — bol, cert, desadv, inv, itt, lot, po, poc, prodorder, recadv, rma, testprd, testres, upevt — and the four that matter to any trading-partner flow are exactly the four your EDI mailbox is full of: the purchase order, the despatch advice, the invoice, and the receiving advice. On the despatch advice the spec even names your world's word for it:
"Despatch Advice. A document/message by means of which the seller or consignor informs the consignee about the despatch of goods. Also called an 'Advanced Shipment Notice,' but the value desadv is always used regardless of local nomenclature."
That is CBV 2.0 §7.3, verbatim. The committee that wrote the event vocabulary looked directly at the EDI document set and made those documents first-class citizens of the why dimension.
Then §8.5 makes the join universal: business-transaction identifiers populate the bizTransactionList field in all EPCIS event types. Not only TransactionEvent — every ObjectEvent, every AggregationEvent, every TransformationEvent can carry the references that say which PO and which ASN this observed moment belongs to. The join is not a vendor feature. It is core schema.
What the reference actually looks like
The identifier syntax is specified too: a business-transaction reference is an identifier — a GDTI EPC URI or a GS1 Digital Link URI for GS1-keyed documents, or any URN your trading community agrees on — paired with its CBV type. Here is the committed fixture from this repository's golden corpus (golden-corpus/valid-standard/transaction-event-po.json), a TransactionEvent asserting two serialized units against a purchase order, with organisation-grain parties in sourceList/destinationList:
{
"type": "TransactionEvent",
"eventTime": "2026-07-19T09:15:00.000Z",
"bizTransactionList": [
{ "type": "po",
"bizTransaction": "urn:epc:id:gdti:0614141.06012.1234" }
],
"action": "ADD",
"epcList": [
"urn:epc:id:sgtin:0614141.107346.2019",
"urn:epc:id:sgtin:0614141.107346.2020"
],
"bizStep": "shipping",
"disposition": "in_transit",
"sourceList": [
{ "type": "owning_party", "source": "urn:epc:id:pgln:0614141.00001" }
],
"destinationList": [
{ "type": "owning_party", "destination": "urn:epc:id:pgln:0614142.00001" }
]
}
Run it through the validator this origin pins:
$ npx epcis.dev validate transaction-event-po.json
valid schema=epcis-json-schema.json version=2.0.1
$ echo $?
0
The schema is GS1's own artifact, vendored and sha256-pinned — the pins are printed on the conformance page and by npx epcis.dev pins. Nothing about this event is an extension: the PO reference, the CBV type, the party GLNs are all fields the official 2.0.1 schema validates.
Why this matters at build-vs-buy altitude
If you run engineering for a supply-chain product, the design consequence is worth stating flatly: the document/event join is designed-in, and the systems that treat documents and events as separate worlds are ignoring the standard's own architecture. Every home-grown order_ref column, every side-table that maps shipment IDs to scan batches, is a reimplementation of bizTransactionList — one you maintain forever, against every partner, without the vocabulary that makes it queryable across company boundaries.
The layered reading:
- The document names the intent. A po reference on an event says which commercial agreement this movement serves.
- The event names the observation. Time, place, objects, step — the dimensions the document cannot witness.
- The join is one field. bizTransactionList on any event type (§8.5), with typed references (§7.3) and organisation-grain parties beside it (§7.4).
That is the whole thesis of the business-transaction layer on this origin: X12 in, EDIFACT in, API-native JSON in — the same bizTransaction references and party GLNs out, on conformant events. The door is /business-transactions/; the join-by-join spec argument is Business transactions in EPCIS; and the document-side mappings are worked end to end in the 856 join and the DESADV join.
See it in the standard, then in a fixture
The verdict compresses to one sentence: the join is designed-in, and almost nobody ships it. You can check the first half against the public CBV 2.0 text in ten minutes — §7.3 for the types, §8.5 for the all-event-types rule — and the second half against any vendor's API surface, including ours.
The next posts in this series work the join end to end: an 856 mapped onto aggregation events, the EDIFACT DESADV at full dictionary depth, and reconciling what the ASN said against what the scanner saw.
Run the fixture now: npx epcis.dev validate on the corpus event above — no account, exit code is the answer. Compile your documents down to events: the document verbs ride the capture surface — get a capture key and say what you will capture.
epcis.dev conforms to EPCIS 2.0 and CBV 2.0 and makes no claim above them. GS1 has not reviewed or endorsed it.
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.