- Outbound: the implementer notifies you. This covers webhook endpoints, the event object, signatures, retries and redelivery.
- Inbound: events flow into the implementer, either as carrier-native pushes received per connection, or as canonical events a client injects (its own last-mile fleet, a carrier with no API, a 3PL feed).
1. The event object
data.objectis the full resource rendered at the endpoint’sapi_version. It is never a diff.data.previous_attributesis present on*.updatedand*.status_changedevents and lists only the changed fields.request.idlinks the event to the API call that caused it. It isnullfor carrier-originated events.- Events are retained and listable for 30 days.
2. Event catalog
enabled_events accepts * (everything) and resource wildcards (tracker.*).
3. The webhook_endpoint object
secretis returned only on create and on rotate.statusisenabled,disabled(by the user), orauto_disabled(after 3 consecutive days of failures).filtersnarrow delivery beyond event type. Every listed filter must match. This keeps multi-tenant platforms from receiving every merchant’s events.
Endpoints
urlMUST behttps. Implementers MUST refuse private, loopback and link-local addresses, which protects against SSRF.rotate_secretkeeps the old secret valid forexpire_previous_inseconds (0–7 days). During overlap, deliveries are signed with both.testsends a synthetic event built from a realistic fixture of the requested type. The event haslivemode: falseand is not stored in/v1/events.
4. Delivery and signatures
Each delivery is an HTTPSPOST with the event JSON as the body, and these headers:
signed_payload = "{t}.{raw_body}"v1 = hex(HMAC_SHA256(secret, signed_payload))- The receiver checks that any
v1matches using a constant-time comparison, and that|now − t| ≤ 300s.
v1a= with Ed25519, public key at /.well-known/shipping-configuration → webhook_signing_keys), so receivers can verify without holding a shared secret. This matters for agents that subscribe dynamically.
Retries and ordering
- A
2xxresponse within 10 seconds is success. Anything else is a failure, and the body is ignored. - Retries use exponential backoff with jitter over 3 days: 1m, 5m, 30m, 2h, 6h, then every 12h.
- No ordering guarantee. Use
data.object.updatedor trackerstatus_updated_atto discard stale events. - At-least-once delivery. Deduplicate on
Shipping-Event-Id, which is the same on every attempt. Each attempt is its ownwebhook_deliverywith a newwhd_ID (Shipping-Delivery-Id) and an incrementingattempt.
The webhook_delivery object
5. Events API
/v1/events with starting_after is a supported alternative to webhooks for clients that cannot receive inbound HTTP, such as agents behind NAT.
6. Inbound: carrier push receivers
Many carriers push tracking (and sometimes invoice adjustments) to a URL. Each connection that supports push gets a dedicated receiver:- The URL is exposed as
connection.inbound.url. The implementer registers it with the carrier automatically where the carrier has a subscription API, and otherwise the shipper pastes it into the carrier portal. - The body is carrier-native. The implementer verifies the carrier’s own signature scheme, which is configured in the carrier’s
inbounddescriptor (chapter 09), maps the payload to canonical events, and responds the way the carrier expects. - Each receipt creates an
inbound_eventwithsource: "carrier_push". (inbound_event.sourceiscarrier_pushfor this receiver andapifor §7. It describes how the event entered the API. The resulting tracking events carrytracking_event.sourcevaluescarrier_pushandinjectedrespectively.) Payloads that fail signature verification are rejected with401, logged, and not stored.
7. Inbound: event injection (canonical)
For sources that have no carrier integration (a merchant’s own delivery fleet, a regional courier using spreadsheets, a 3PL’s scan feed, a carrier implementing this spec that pushes canonical events), clients inject events directly:
The response is an
inbound_event:
source: "injected" on the tracker and fire the usual tracker.* events. Injection requires the trackers:write scope, and injection into a tracker owned by a carrier connection additionally requires inbound:write, so that a leaked read key cannot fake deliveries.
The sequence shows the injection example above, a merchant fleet recording a delivery.
8. Test helpers
standard (pre_transit → in_transit ×3 → out_for_delivery → delivered), failed_attempt (… → exception/delivery_attempted → out_for_delivery → delivered), customs_hold, return_to_sender, lost.
Also available: POST /v1/test_helpers/shipments/{id}/charge_adjustment, which simulates a carrier reweigh.