# A delivered webhook is evidence, not a receiving advice

> transactions.dev · by visibility.cloud — 2026-07-31
> Written for the supply-chain ISV / traceability-vendor engineering org — the integration engineer deciding what the tracking feed is allowed to claim.

One thesis: **the parcel rails know that a package with a tracking number
reached an address, and they do not know what was in it, who owns it, or
who took custody — so the pipeline compiles a `delivered` webhook to
corroborating evidence attached to the record, and refuses, as a design
law, to mint a `recadv` reference or a receiving ObjectEvent from it.**

## What the aggregators are genuinely great at

Credit first, because the refusal only means something if the capability
is stated fairly. EasyPost, Shippo, and ShipEngine are the cheapest event
feed in our entire connector matrix: one REST integration, self-serve
keys, test modes, HMAC-signed webhooks, and normalized status streams
across dozens of carriers each. EasyPost's `Tracker` fires
`tracker.updated` through to delivery with `signed_by` when the carrier
captured one; Shippo's `track_updated` normalizes to statuses like
`DELIVERED` with a mock carrier for testing; ShipEngine tracks to a
`status_code` of `DE`. If the question is "where is this package, across
forty carriers, without forty integrations," the aggregators are the
right answer and this layer uses them exactly that way.

## The identifier audit

Now the audit that decides what those feeds may claim, run against each
aggregator's own documentation. The universal join keys are
`tracking_code`/`tracking_number` plus `carrier` — and that is the whole
list. No GTIN anywhere in the shipment surface. No GLN; addresses are
structured text. No SSCC. The closest thing to product identity sits at
the customs line: EasyPost's `CustomsItem` carries `hs_tariff_number`,
Shippo's customs items carry `hs_code` and a `sku_code`, ShipEngine's
customs `products` carry a harmonized code and `sku`. Tariff grain and
SKU grain — export paperwork, not item identity.

Hold that against what a receiving claim requires. A
[`recadv` reference](/blog/biztransaction-is-a-core-field/) asserts a
business document: *these goods, against this despatch advice, accepted by
this party.* A receiving ObjectEvent asserts custody: *this identified
thing crossed this dock.* The parcel feed has no *what* at either grain —
no EPC, no GTIN, no lot — no *who* at any grain, and its *where* is an
address string. Every load-bearing dimension of the claim is absent from
the wire.

## The two compiles, side by side

The wrong compile, shown so it can be refused explicitly:

```json
{
  "type": "ObjectEvent",
  "action": "OBSERVE",
  "bizStep": "receiving",
  "quantityList": [ { "epcClass": "???", "quantity": 1 } ],
  "bizTransactionList": [
    { "type": "recadv", "bizTransaction": "urn:trk:usps:9400111899560000000000" }
  ]
}
```

Everything wrong is visible: an invented quantity against an unknowable
class, a tracking number wearing a `recadv` costume, a custody assertion
no one made. A downstream consumer cannot distinguish this fabrication
from a real goods receipt — which is precisely the property a defensible
record cannot afford.

The right compile attaches the same fact as what it is — evidence
corroborating an expectation the paperwork already created:

```json
{
  "evidence": {
    "kind": "parcel_delivery",
    "source": "easypost",
    "tracking": "9400111899560000000000",
    "carrier": "USPS",
    "status": "delivered",
    "signed_by": "R GARCIA",
    "at": "2026-07-29T18:41:00Z",
    "corroborates": {
      "bizTransaction": { "type": "desadv", "ref": "urn:epc:id:gdti:0614141.00001.F10442" }
    }
  }
}
```

The delivery joins the record through the one key both sides share — the
tracking number the fulfillment wrote when
[the shipment was compiled](/blog/ecommerce-has-no-invoice-object/) — and
it strengthens the `desadv` expectation without impersonating its close.
When the [reconcile verb](/blog/reconcile-is-a-verdict/) runs, the
evidence weighs in: an expectation with a delivered corroboration and no
receiving event is a different verdict class than one with neither. The
receiving event itself comes only from a surface entitled to assert it —
[a WMS receiving object](/blog/shipbob-wro-is-a-receiving-advice/), an
ERP goods receipt, a scan at the dock.

Evidence-for versus record-of. It is the same law this layer applies to
[the 214 carrier status in the X12 dialect](/blog/the-214-is-not-an-event-record/),
and it is the line that keeps the record defensible: nothing on the spine
asserts more than its source knew.

## The exceptions worth knowing

Two carrier-direct facts from the matrix sharpen the picture rather than
soften it. FedEx Ground **accepts SSCC-18 ("00") barcodes** on labels —
an acceptance, which makes it a labeling opportunity for shippers who
serialize logistic units, not a data feed; the SSCC still originates in
your systems and [the ASN rail](/blog/the-856-is-already-an-aggregation-tree/).
And UPS's Quantum View subscription carries manifest-grade inbound and
outbound visibility — the closest thing to a native ASN analog in parcel —
plus Track Alert webhooks. Richer evidence, better corroboration,
account-gated onboarding; still evidence. The grain that would upgrade a
parcel feed into a custody record — item identity, party identity, an
attested receiver — never appears on the parcel wire, and
[almost nowhere else either](/blog/the-gtin-gap/).

## The posture, generalized

A pipeline earns trust by what it refuses to fabricate. The aggregators
are in the connector matrix because cheap corroboration at forty-carrier
scale is genuinely valuable; they compile to evidence because that is what
they carry. The executive consequence — exception views where a delivered
parcel with no receiving close is a flag, not a silent assumption — is
[visibility.cloud](https://visibility.cloud/asn-po-context-in-traces)'s
layer over the same record.

If your delivery telemetry runs through EasyPost, Shippo, ShipEngine, or
a carrier direct, [say which](/get-access/) — the evidence intake is
built per rail, in the order the answers arrive.

---
Access: https://transactions.dev/get-access/ · All posts: https://transactions.dev/blog/ · Machine face: https://transactions.dev/llms.txt
