Relay emits 38 event types. This page is the complete catalog: what each one means, exactly when it fires, and the body it delivers. Register an endpoint with POST /v2/webhooks and name the ones you want in enabled_events — you receive those and nothing else. To receive everything, register enabled_events: ["*"]: the wildcard matches every type on this page and every type added in a later release, so it never needs re-registering as the catalog grows. Signing and verification are covered in Authentication; the wire contract is in the API Reference.
Most merchant integrations need about nine of these. The rest exist for warehouse operations, admin auditing, and claims workflows — subscribe to them only if you run those.
| Event | Why you want it |
|---|---|
| shipment.label_ready | The label or QR is fetchable — send it to the shopper. |
| shipment.in_possession | We physically have the parcel. This is the event that ends “where is my return?” tickets, and where most merchants start the refund clock. |
| shipment.injected | Handed to the onward carrier; carrier is populated here. |
| shipment.delivered | Terminal happy path, with proof of delivery. |
| shipment.undeliverable shipment.return_to_sender | Terminal sad path. Both are reachable from one rts request — handle the pair. |
| shipment.cancelled shipment.expired | The shopper never sent it. Close the RMA. |
| shipment.adjusted | The price changed after the quote. Reconcile billing. |
| shipment.discrepancy_reported | Contents did not match the declaration. |
Every delivery is the same record, whatever the event. Four blocks: what it is, every id it can be found by, what happened, and — only when the event type has fields of its own — the handful that are specific to it.
{
"object": "webhook",
"eventID": "evt_0807c0",
"event": "scan-event",
"type": "shipment.delivered",
"identifiers": {
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 delivered",
"email": "",
"full_name": "",
"status": "delivered",
"direction": "return"
},
"eventSpecific": {
"scan-type": "delivered",
"scantime": "2026-08-09T16:04:00Z",
"scannerEmail": "",
"scannerName": "",
"pod": "/v2/documents/doc_pod_7Kd93a.jpg",
"podGrade": "A",
"carrier": "Veho"
}
}
| Block | What it holds |
|---|---|
type | The exact event type, always — the same string you named in enabled_events. This is what your handler switches on. |
event | The internal LMS name for the event. Usually identical to type, but the ten handling moments all collapse to "scan-event" here. Do not switch on this. |
eventID | The event's id. Stable across retries of the same delivery — use it to make your handler idempotent. |
identifiers | Every id this event can be joined on: the parcel, your own order and RMA numbers, the tracking and barcode, the subscription that received it. Keys are absent rather than null when they do not apply. |
event_info | When it happened (eventDateTime), a one-line summary, the resulting status, and where — location, lat/lon — on the events that happen somewhere. |
eventSpecific | Only the fields this event type has. Omitted entirely when there are none, which is the common case — it is never an empty object. |
type, never on event. type is always present and always the exact type you subscribed to. event is our internal name: shipment.injected, shipment.delivered, shipment.in_possession and seven other handling moments all arrive with event: "scan-event", discriminated there only by eventSpecific["scan-type"] — which uses a third spelling again ("possession", not "in_possession").GET /v2/shipments/{id} — or the event feed at GET /v2/events, keyed by the eventID above, when you need the whole record.shipment.adjusted and shipment.discrepancy_reported both start with shipment. and neither is about a shipment as such — they report an adjustment and a discrepancy. identifiers.shipment is on both, so the parcel is always reachable.enabled_events, so a new type cannot surprise an existing subscription — but do not assume this list is closed. The exception is deliberate: a wildcard subscription (["*"]) receives new types as soon as they ship, which is the reason to choose it. If your handler switches on type, give it a default branch before you subscribe that way.milestones and status, not by arrival.2xx is retried, so the same event can arrive twice. Treat eventID as an idempotency key.qr.scanned, shipment.in_possession and, on a reweigh, shipment.adjusted. A retail sale fires shipment.created, shipment.label_ready and shipment.sold.GET /v2/events is the durable feed of every event emitted for your account, retained 365 days. After a webhook outage, pull the window you missed from there rather than asking for a replay.Everything that happens before the parcel physically moves. If you are only going to handle a handful of events, shipment.label_ready is the one that tells a shopper their label is fetchable.
A shipment has been ingested and now exists in Relay. It has an id, a tracking number and a barcode; whether it also has a label yet depends on fulfillment.
Fires whenPOST /v2/shipments, POST /v2/shipments/batch (once per member), and POST /v2/retail/shipments (which also fires shipment.sold).
print_label shipment fires shipment.label_ready in the same call, immediately after this one. A qr_code shipment does not — there is no label until the QR is presented at a counter.carrier is null here in a marketplace-decision market. It is populated at injection, not at creation.{
"object": "webhook",
"eventID": "evt_62f467",
"event": "shipment.created",
"type": "shipment.created",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "shipment.created",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Shipment created for LMS8418253",
"status": "created",
"direction": "return"
},
"eventSpecific": {
"labelVersion": 1,
"serviceLevel": "ground",
"parcel": {
"weight": 2.5,
"length": 12,
"width": 9,
"height": 4,
"cubicFt": 0.25
}
}
}The label is generated and fetchable from GET /v2/shipments/{id}/label. This is the event to hang "your return label is ready" on.
Fires whenAt creation for fulfillment: "print_label". For fulfillment: "qr_code", when the label is produced on site — the QR is scanned at a PackageHub counter.
print_label shipment that emitted it at creation does not emit it again when the lifecycle passes through the label_ready status — that suppression is deliberate.shipment.label_reprinted.{
"object": "webhook",
"eventID": "evt_677c21",
"event": "shipment.label_ready",
"type": "shipment.label_ready",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.label_ready",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Label ready for LMS8418253",
"status": "label_ready",
"direction": "return",
"labelUrl": "/v2/shipments/shp_7Kd93a/label"
},
"eventSpecific": {
"labelFormat": "pdf",
"labelVersion": 1,
"serviceLevel": "ground",
"parcel": {
"weight": 2.5,
"length": 12,
"width": 9,
"height": 4,
"cubicFt": 0.25
}
}
}A fresh label was minted to replace the current one. The old label is voided.
Fires whenPOST /v2/shipments/{id}/label/reprint
label_version increments and supersedes names the label_id just replaced. If you cache label bytes, key the cache on label_id, not on the shipment — otherwise a reprint serves the voided label.{
"object": "webhook",
"eventID": "evt_21fd1e",
"event": "shipment.label_reprinted",
"type": "shipment.label_reprinted",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_9Xr2Qm",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.label_reprinted",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Label reprinted for LMS8418253",
"status": "label_ready",
"direction": "return",
"labelUrl": "/v2/shipments/shp_7Kd93a/label"
},
"eventSpecific": {
"labelVersion": 2,
"serviceLevel": "ground",
"parcel": {
"weight": 2.5,
"length": 12,
"width": 9,
"height": 4,
"cubicFt": 0.25
}
}
}A pre-drop-off amendment was accepted — parcel dimensions, declared value, destination or drop-off routing.
Fires whenPATCH /v2/shipments/{id}
amount can move. If the new price or the drop-off routing changes, a new label is minted too — label_version bumps and supersedes is set, exactly as on a reprint.409 shipment_locked once the parcel has been scanned at a PackageHub, so this event never arrives after shipment.ready_for_pickup.{
"object": "webhook",
"eventID": "evt_21f373",
"event": "shipment.updated",
"type": "shipment.updated",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_9Xr2Qm",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.updated",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 updated",
"status": "created",
"direction": "return"
}
}A counter sale completed: a walk-in customer handed a package over, it was priced and paid for, and the label printed on site.
Fires whenPOST /v2/retail/shipments
direction: "outbound" and fulfillment: "print_label" — the customer is standing at the counter, so there is no QR to present later.shipment.created and shipment.label_ready, not instead of them. Subscribe to shipment.sold only if you need to distinguish counter sales; channel: "retail" already tells you on any of the three.{
"object": "webhook",
"eventID": "evt_5ac685",
"event": "shipment.sold",
"type": "shipment.sold",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.sold",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 sold at the counter",
"status": "label_ready",
"direction": "outbound",
"labelUrl": "/v2/shipments/shp_7Kd93a/label"
},
"eventSpecific": {
"soldAt": "2026-08-05T14:46:45Z",
"serviceLevel": "reach_coverage"
}
}The parcel is physically moving through our network. In production these are driven by real PackageHub and driver scans. In sandbox you drive them yourself, one rung per call, with POST /v2/test/shipments/{id}/advance.
A customer's QR code was presented and validated at a drop-off counter.
Fires whenPOST /v2/scan-events/walk-in
identifiers.qr names which, and identifiers.shipment the parcel it belongs to.occurred_at is when the QR was presented at the counter. It is not the same as event_info.eventDateTime, which is when we emitted the event — the two diverge whenever the emit is queued or replayed. Use occurred_at for anything that reasons about custody time.store is the drop-off location resolved from store_id, with its coordinates. It is absent when store_id is not a location we know — never a placeholder, so a missing store reads as unknown rather than as somewhere.shipment.in_possession for the same shipment, and by shipment.adjusted if the counter's reweigh disagreed with the declared parcel.{
"object": "webhook",
"eventID": "evt_e9e4ed",
"event": "scan-event",
"type": "qr.scanned",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"tracking": "LM7KD93AQ2",
"store": "loc_a1",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "QR for LM7KD93AQ2 presented at a drop-off location",
"email": "",
"full_name": "",
"lat": "32.784395",
"lon": "-96.782513",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"location": {
"name": "PackageHub Deep Ellum",
"code": "PH-A1",
"city": "Dallas",
"state": "TX",
"postalCode": "75226"
}
},
"eventSpecific": {
"scan-type": "dropoff",
"scantime": "2026-08-09T16:04:00Z",
"scannerEmail": "",
"scannerName": "",
"storeCode": "PH-A1",
"pudoCode": "CPU"
}
}A drop-off location has accepted the package and it is waiting on the shelf for our sweep.
Fires whenPOST /v2/scan-events/accept — the counter's explicit acceptance of a package that arrived with its label already applied.
print_label counterpart to shipment.in_possession. A pre-labelled parcel is accepted; a QR parcel is scanned, labelled and taken into custody in one step.{
"object": "webhook",
"eventID": "evt_28a2be",
"event": "scan-event",
"type": "shipment.ready_for_pickup",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 accepted at the counter",
"email": "",
"full_name": "",
"lat": "32.879232",
"lon": "-96.960262",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "ready_for_pickup",
"direction": "return"
},
"eventSpecific": {
"scan-type": "accepted",
"scantime": "2026-08-09T16:04:00Z",
"scannerEmail": "",
"scannerName": ""
}
}A driver collected the parcel from the drop-off location on a sweep.
Fires whenA driver collection scan.
{
"object": "webhook",
"eventID": "evt_6810d4",
"event": "scan-event",
"type": "shipment.picked_up",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 collected",
"email": "",
"full_name": "",
"lat": "32.914406",
"lon": "-96.938682",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "picked_up",
"direction": "return"
},
"eventSpecific": {
"scan-type": "pickup",
"scantime": "2026-08-05T21:05:00Z",
"scannerEmail": "",
"scannerName": ""
}
}The parcel is in our custody. This is the event that ends "where is my return?" tickets — it is the first hard confirmation the customer actually sent the thing.
Fires whenPOST /v2/scan-events/walk-in (QR presented at a counter), or a custody scan.
shipment.delivered.{
"object": "webhook",
"eventID": "evt_9f34fb",
"event": "scan-event",
"type": "shipment.in_possession",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 taken into LMS possession",
"email": "",
"full_name": "",
"lat": "32.879261",
"lon": "-96.959788",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "in_possession",
"direction": "return"
},
"eventSpecific": {
"scan-type": "possession",
"scantime": "2026-08-05T21:05:00Z",
"scannerEmail": "",
"scannerName": ""
}
}The parcel arrived at the processing facility.
Fires whenA facility arrival scan.
{
"object": "webhook",
"eventID": "evt_ab45c5",
"event": "scan-event",
"type": "shipment.at_sort_center",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 arrived at the sort centre",
"email": "",
"full_name": "",
"lat": "32.87889",
"lon": "-96.959918",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "at_sort_center",
"direction": "return"
},
"eventSpecific": {
"scan-type": "arrived_at_sort",
"scantime": "2026-08-06T02:40:00Z",
"scannerEmail": "",
"scannerName": ""
}
}The parcel has been processed at the facility and assigned to an onward lane.
Fires whenA sort scan.
{
"object": "webhook",
"eventID": "evt_d85a53",
"event": "scan-event",
"type": "shipment.sorted",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 sorted",
"email": "",
"full_name": "",
"lat": "32.878839",
"lon": "-96.959807",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "sorted",
"direction": "return"
},
"eventSpecific": {
"scan-type": "sorted",
"scantime": "2026-08-06T02:40:00Z",
"scannerEmail": "",
"scannerName": ""
}
}The parcel was handed to the onward carrier. Our leg is done.
Fires whenCarrier injection, or acceptance of an inject tender (POST /v2/tenders/{id}/accept).
carrier is populated here in a marketplace-decision market, where it was null from creation onward. If you display a carrier to the customer, this is the earliest event you can reliably read it from.tender_id is set when the injection came through a tender.{
"object": "webhook",
"eventID": "evt_8f0ec1",
"event": "scan-event",
"type": "shipment.injected",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"tender": "tnd_4Km2Xp",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 handed to the onward carrier",
"email": "",
"full_name": "",
"lat": "32.841026",
"lon": "-96.996314",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "injected",
"direction": "return"
},
"eventSpecific": {
"scan-type": "injected",
"scantime": "2026-08-06T02:40:00Z",
"scannerEmail": "",
"scannerName": "",
"carrier": "Veho"
}
}The onward carrier has the parcel. These mirror carrier scans, so their cadence is the carrier's, not ours.
The carrier is moving the parcel.
Fires whenThe first carrier movement scan after injection.
tracking.updated fires alongside this one, always, and only here. If you subscribe to both you will receive two webhooks carrying the identical Shipment payload.{
"object": "webhook",
"eventID": "evt_682596",
"event": "shipment.in_transit",
"type": "shipment.in_transit",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "shipment.in_transit",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 in transit",
"lat": "32.889233",
"lon": "-97.01168",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "in_transit",
"direction": "return"
},
"eventSpecific": {
"carrier": "Veho",
"scheduledDeliveryDate": "2026-08-09"
}
}Tracking information changed.
Fires whenAlongside shipment.in_transit, and only there.
GET /v2/tracking/{tracking} for the full scan history; do not expect it to arrive by webhook.{
"object": "webhook",
"eventID": "evt_aee388",
"event": "tracking.updated",
"type": "tracking.updated",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "tracking.updated",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Tracking updated for LMS8418253",
"lat": "32.912409",
"lon": "-96.906439",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "in_transit",
"direction": "return"
},
"eventSpecific": {
"carrier": "Veho",
"scheduledDeliveryDate": "2026-08-09"
}
}The parcel is on a vehicle for final delivery.
Fires whenAn out-for-delivery carrier scan — or a redeliver disposition, which puts a failed parcel back on this rung.
redeliver disposition deliberately reuses this event rather than minting a new one: the parcel has rejoined the ordinary ladder, so the ladder's own event is the truthful signal. You may therefore see it twice for one shipment.{
"object": "webhook",
"eventID": "evt_fb9961",
"event": "scan-event",
"type": "shipment.out_for_delivery",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 out for delivery",
"email": "",
"full_name": "",
"lat": "32.826213",
"lon": "-96.951917",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "out_for_delivery",
"direction": "return"
},
"eventSpecific": {
"scan-type": "out_for_delivery",
"scantime": "2026-08-09T09:12:00Z",
"scannerEmail": "",
"scannerName": "",
"carrier": "Veho",
"scheduledDeliveryDate": "2026-08-09"
}
}Delivered. Terminal, and the happy path.
Fires whenA delivery scan.
pod and podGrade are populated at delivery and only at delivery. Fetch the image through GET /v2/shipments/{id}/pod.{
"object": "webhook",
"eventID": "evt_0807c0",
"event": "scan-event",
"type": "shipment.delivered",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 delivered",
"email": "",
"full_name": "",
"lat": "32.878688",
"lon": "-96.960406",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "delivered",
"direction": "return"
},
"eventSpecific": {
"scan-type": "delivered",
"scantime": "2026-08-09T16:04:00Z",
"scannerEmail": "",
"scannerName": "",
"pod": "/v2/documents/doc_pod_7Kd93a.jpg",
"podGrade": "A",
"carrier": "Veho"
}
}The parcel could not complete its journey. Each of these is the resolution of a failed delivery, recorded through POST /v2/shipments/{id}/disposition — except shipment.rejected, which happens at the counter before we ever take custody.
A drop-off location refused the package. We never took custody.
Fires whenPOST /v2/scan-events/reject
reject_reason, reject_reason_note and rejected_at.{
"object": "webhook",
"eventID": "evt_864fd4",
"event": "scan-event",
"type": "shipment.rejected",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "scan-event",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 rejected at the counter",
"email": "",
"full_name": "",
"lat": "32.878682",
"lon": "-96.959687",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "rejected",
"direction": "return"
},
"eventSpecific": {
"scan-type": "rejected",
"scantime": "2026-08-09T16:04:00Z",
"scannerEmail": "",
"scannerName": "",
"rejectReason": "prohibited_item",
"rejectReasonNote": "Contains a lithium battery pack."
}
}Delivery failed and the parcel is not in our custody — the carrier still has it.
Fires whenPOST /v2/shipments/{id}/disposition with disposition: "rts", when the parcel is past injection.
rts request produces a different event depending on where the parcel is. Out of our custody it lands here; already in our custody it lands on shipment.return_to_sender. Handle both.rts starts the return leg, and a redeliver puts it back out for delivery.{
"object": "webhook",
"eventID": "evt_22f904",
"event": "shipment.undeliverable",
"type": "shipment.undeliverable",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.undeliverable",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 could not be delivered",
"email": "",
"full_name": "",
"lat": "32.878854",
"lon": "-96.960359",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "undeliverable",
"direction": "return"
},
"eventSpecific": {
"disposition": "rts",
"carrier": "Veho"
}
}The parcel is on its way back to the sender. Terminal.
Fires whenPOST /v2/shipments/{id}/disposition with disposition: "rts", when the parcel is already in our custody (status undeliverable or unclaimed).
409 shipment_not_dispositionable is returned if you try.{
"object": "webhook",
"eventID": "evt_a71604",
"event": "shipment.return_to_sender",
"type": "shipment.return_to_sender",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.return_to_sender",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 returning to sender",
"lat": "32.878781",
"lon": "-96.959584",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "return_to_sender",
"direction": "return"
},
"eventSpecific": {
"disposition": "rts",
"carrier": "Veho"
}
}Nobody collected the parcel and the holding clock has started.
Fires whenPOST /v2/shipments/{id}/disposition with disposition: "unclaimed".
dispose_after is the date the parcel becomes eligible for disposal — 30 days out. Use it to drive a "last chance" notification.dispose_after out forever, so a repeat report re-fires this event with the original dates.{
"object": "webhook",
"eventID": "evt_ffbf00",
"event": "shipment.unclaimed",
"type": "shipment.unclaimed",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.unclaimed",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 unclaimed",
"lat": "32.878702",
"lon": "-96.960319",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "unclaimed",
"direction": "return"
},
"eventSpecific": {
"disposition": "unclaimed"
}
}The parcel was destroyed or otherwise disposed of. Terminal.
Fires whenPOST /v2/shipments/{id}/disposition with disposition: "dispose".
unclaimed_since and dispose_after are deliberately retained on a terminal outcome — they are the record of the aging that justified it.{
"object": "webhook",
"eventID": "evt_2c8445",
"event": "shipment.disposed",
"type": "shipment.disposed",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.disposed",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 disposed of",
"lat": "32.878959",
"lon": "-96.960299",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "disposed",
"direction": "return"
},
"eventSpecific": {
"disposition": "dispose"
}
}A claim was opened against a shipment — lost, damaged or misdelivered.
Fires whenPOST /v2/claims
amount_approved is null at opening. There is currently no event for a claim being approved, denied or closed — poll GET /v2/claims/{id}.{
"object": "webhook",
"eventID": "evt_065c97",
"event": "claim.opened",
"type": "claim.opened",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"claim": "clm_5Rt8Wq",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "claim.opened",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Claim clm_5Rt8Wq opened against shp_7Kd93a",
"status": "open"
},
"eventSpecific": {
"claimType": "lost",
"description": "Parcel not scanned since the sort centre; customer refunded."
}
}The shipment stopped moving, by request or by the clock.
The shipment was cancelled and its label voided.
Fires whenPOST /v2/shipments/{id}/cancel
label_voided flips to true. A voided label must not be presented at a counter; if you have already emailed it, this is the event to send the retraction on.{
"object": "webhook",
"eventID": "evt_e235ec",
"event": "shipment.cancelled",
"type": "shipment.cancelled",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.cancelled",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 cancelled",
"status": "cancelled",
"direction": "return"
},
"eventSpecific": {
"labelVoided": true,
"voidedAt": "2026-08-05T16:30:00Z",
"cancelledAt": "2026-08-05T16:30:00Z",
"expiresAt": "2026-08-08T14:46:45Z"
}
}The customer never sent the parcel and the window closed.
Fires whenLazily — when the shipment is next read after expires_at has passed, and once only.
expires_at — which may be well after that timestamp, or not at all until something asks. Do not treat its arrival time as the expiry time; read expired_at.{
"object": "webhook",
"eventID": "evt_205357",
"event": "shipment.expired",
"type": "shipment.expired",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m",
"qr": "qr_htBmbq"
},
"event_info": {
"event": "shipment.expired",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 expired before it was ever possessed",
"status": "expired",
"direction": "return"
},
"eventSpecific": {
"labelVoided": true,
"voidedAt": "2026-08-08T14:46:45Z",
"expiredAt": "2026-08-08T14:46:45Z",
"expiresAt": "2026-08-08T14:46:45Z"
}
}The shipment was placed on hold and will not advance until released.
Fires whenPOST /v2/shipments/{id}/hold
status does not change — the shipment keeps whatever rung it was on. The hold lives in the boolean held field, so a handler that switches only on status will miss it entirely.{
"object": "webhook",
"eventID": "evt_fa9f96",
"event": "shipment.held",
"type": "shipment.held",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.held",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 held",
"status": "created",
"direction": "return"
},
"eventSpecific": {
"held": true
}
}A hold was lifted and the shipment may advance again.
Fires whenPOST /v2/shipments/{id}/release
{
"object": "webhook",
"eventID": "evt_b3fa5e",
"event": "shipment.released",
"type": "shipment.released",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.released",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 released from hold",
"status": "created",
"direction": "return"
},
"eventSpecific": {
"held": false
}
}The destination address was changed in flight.
Fires whenPOST /v2/shipments/{id}/redirect
to. The tracking number does not change.{
"object": "webhook",
"eventID": "evt_0fb955",
"event": "shipment.redirected",
"type": "shipment.redirected",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"barcode": "LMS8418253",
"tracking": "LM7KD93AQ2",
"rma": "RMA-4471",
"orderID": "ORD-88123",
"shipExtID": "OC-ORDER-55219",
"label": "lbl_pfiCYF",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.redirected",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "LMS8418253 redirected",
"status": "in_transit",
"direction": "return",
"location": {
"name": "Dana Reyes",
"city": "Dallas",
"state": "TX",
"postalCode": "75202",
"country": "US"
}
},
"eventSpecific": {
"redirectedTo": "Dana Reyes",
"redirectedToLine1": "77 Elm St",
"carrier": "Veho"
}
}Anything that changes what the shipment costs after it was quoted. If you reconcile invoices, these are the events that matter most.
The price changed after the fact, because the parcel was not what was declared.
Fires whenThree different places: a reweigh/re-dim at the counter (POST /v2/scan-events/walk-in), an explicit POST /v2/shipments/{id}/adjustments, and a chargeable discrepancy (POST /v2/discrepancies).
shipment.. Read shipment_id to find the parcel.delta_amount is signed — a re-weigh can go down. All money is in minor units (cents).shipment.discrepancy_reported, and the discrepancy payload's adjustment_id points at this adjustment.{
"object": "webhook",
"eventID": "evt_b74d79",
"event": "shipment.adjusted",
"type": "shipment.adjusted",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"adjustment": "adj_3Np7Vk",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.adjusted",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "shp_7Kd93a repriced by adjustment adj_3Np7Vk"
},
"eventSpecific": {
"reason": "reweigh",
"originalWeight": 2.5,
"newWeight": 4.0
}
}Declared parcel details did not match what was observed, recorded up front as a formal discrepancy.
Fires whenPOST /v2/discrepancies
stage is dropoff or sort — where the mismatch was caught.chargeable: false means it was recorded but not billed, and adjustment_id is then null.{
"object": "webhook",
"eventID": "evt_a82bd7",
"event": "shipment.discrepancy_reported",
"type": "shipment.discrepancy_reported",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"discrepancy": "dsc_8Qm4Zt",
"adjustment": "adj_3Np7Vk",
"store": "ph_store_4821",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "shipment.discrepancy_reported",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Discrepancy dsc_8Qm4Zt reported against shp_7Kd93a",
"lat": "32.878735",
"lon": "-96.959772",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z"
},
"eventSpecific": {
"stage": "dropoff",
"declared": {
"weight": 2.5,
"length": 12,
"width": 9,
"height": 4
},
"observed": {
"weight": 4.0,
"length": 14,
"width": 10,
"height": 5
},
"chargeable": true
}
}A partner formally disputed a price adjustment.
Fires whenPOST /v2/adjustments/{adj_id}/dispute
shipment.adjusted, with dispute_status and dispute_reason now populated.GET /v2/shipments/{id}/adjustments.{
"object": "webhook",
"eventID": "evt_8a014b",
"event": "adjustment.disputed",
"type": "adjustment.disputed",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"adjustment": "adj_3Np7Vk",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "adjustment.disputed",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Adjustment adj_3Np7Vk disputed for shp_7Kd93a"
},
"eventSpecific": {
"reason": "reweigh",
"originalWeight": 2.5,
"newWeight": 4.0,
"disputeStatus": "open",
"disputeReason": "weight_incorrect"
}
}Only relevant if you use fulfillment: "qr_code". Two of these three carry an ad-hoc payload rather than a documented object.
A replacement QR was issued and the previous one invalidated.
Fires whenPOST /v2/shipments/{id}/qr/reissue
object field — this is a bare three-key record, like qr.scanned.{
"object": "webhook",
"eventID": "evt_ea272a",
"event": "qr.reissued",
"type": "qr.reissued",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"qr": "qr_Pw3Nc9",
"previousQr": "qr_htBmbq",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "qr.reissued",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "QR reissued for shp_7Kd93a"
}
}A QR was invalidated and can no longer be presented.
Fires whenPOST /v2/qr/{qr_id}/invalidate
qr.reissued rather than firing this event for the superseded code.{
"object": "webhook",
"eventID": "evt_30fbc9",
"event": "qr.invalidated",
"type": "qr.invalidated",
"identifiers": {
"ltracking": "LR4T8XKP2M",
"shipment": "shp_7Kd93a",
"qr": "qr_htBmbq",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "qr.invalidated",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "QR invalidated for shp_7Kd93a",
"status": "invalidated"
},
"eventSpecific": {
"qrStatus": "invalidated",
"expiresAt": "2026-08-08T14:46:45Z"
}
}Operational events between us and a carrier or a store. Useful to a warehouse or ops dashboard; usually noise to a merchant integration.
A pickup was booked for a shipment.
Fires whenPOST /v2/pickups
{
"object": "webhook",
"eventID": "evt_7adb39",
"event": "pickup.scheduled",
"type": "pickup.scheduled",
"identifiers": {
"ltracking": "LR9BC5HTQ7",
"shipment": "shp_7Kd93a",
"pickup": "pkp_2Fh6Ly",
"confirmation": "PK00UM54LNT0",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "pickup.scheduled",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Pickup pkp_2Fh6Ly scheduled for shp_7Kd93a",
"lat": "32.879237",
"lon": "-96.959706",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "scheduled"
},
"eventSpecific": {
"pickupDate": "2026-08-10",
"pickupWindowStart": "09:00",
"pickupWindowEnd": "17:00",
"packageCount": 1
}
}A booked pickup was cancelled.
Fires whenPOST /v2/pickups/{id}/cancel, and PATCH /v2/pickups/{id} setting status to cancelled.
{
"object": "webhook",
"eventID": "evt_a4b692",
"event": "pickup.cancelled",
"type": "pickup.cancelled",
"identifiers": {
"ltracking": "LR9BC5HTQ7",
"shipment": "shp_7Kd93a",
"pickup": "pkp_2Fh6Ly",
"confirmation": "PK00UM54LNT0",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "pickup.cancelled",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Pickup pkp_2Fh6Ly cancelled for shp_7Kd93a",
"lat": "32.879315",
"lon": "-96.959553",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "cancelled"
},
"eventSpecific": {
"pickupDate": "2026-08-10",
"pickupWindowStart": "09:00",
"pickupWindowEnd": "17:00",
"packageCount": 1
}
}A collection visit was registered for a store and a day.
Fires whena sweep is ordered up — one carrier visit to one store, whatever it ends up collecting.
sweepId, and the carrier is paid once whether the driver takes one box or ten.shipment identifier and no package count, because at this moment there are no parcels on it yet. Parcels join a live sweep as they are handed over. Ask GET /v2/shipments?sweepId=SW7K3D9A for the membership as it stands now.status is ordered at creation. A sweep stays open while any live pickup remains on it; it closes only when every pickup on it has been cancelled.{
"object": "webhook",
"eventID": "evt_35c601",
"event": "sweep.created",
"type": "sweep.created",
"identifiers": {
"sweep": "SW7K3D9A",
"store": "PH4821",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "sweep.created",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Sweep SW7K3D9A created",
"lat": "33.965048",
"lon": "-118.151936",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "ordered",
"location": {
"name": "PackageHub Bell Gardens",
"code": "PH4821",
"city": "Bell Gardens",
"state": "CA",
"postalCode": "90201",
"country": "US"
}
},
"eventSpecific": {
"sweepDate": "2026-08-30",
"dp": "HOV",
"storeCode": "PH4821",
"storeAddress": "6621 Eastern Ave, Bell Gardens, CA 90201"
}
}A batch of shipments was assembled for handoff to a carrier.
Fires whenPOST /v2/tenders
piece_count is derived from the number of member shipments, not taken from your request. A piece_count you send is ignored.shipments[].status is recomputed live on every read, so it reflects each member's current state rather than a snapshot taken at tender time.{
"object": "webhook",
"eventID": "evt_ca6c11",
"event": "tender.created",
"type": "tender.created",
"identifiers": {
"tender": "tnd_4Km2Xp",
"facility": "lms_sort_dfw",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "tender.created",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Tender tnd_4Km2Xp created",
"lat": "32.878721",
"lon": "-96.960023",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "created"
},
"eventSpecific": {
"carrier": "GoFo",
"handoff": "inject",
"pieceCount": 2,
"shipmentCount": 2
}
}The carrier signed for the batch.
Fires whenPOST /v2/tenders/{id}/accept
injected for an inject tender, made_ready for a collect one — so you will also receive a shipment.injected per member.tender.discrepancy.{
"object": "webhook",
"eventID": "evt_cfa373",
"event": "tender.accepted",
"type": "tender.accepted",
"identifiers": {
"tender": "tnd_4Km2Xp",
"facility": "lms_sort_dfw",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "tender.accepted",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Tender tnd_4Km2Xp accepted",
"lat": "32.879026",
"lon": "-96.960193",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "accepted"
},
"eventSpecific": {
"carrier": "GoFo",
"handoff": "inject",
"pieceCount": 2,
"pieceCountReceived": 2,
"shipmentCount": 2,
"acceptedBy": "R. Alvarez",
"acceptedAt": "2026-08-06T14:20:00Z"
}
}The carrier received a different number of pieces than we tendered.
Fires whenPOST /v2/tenders/{id}/accept, when piece_count_received differs from the tender's derived piece_count.
tender.accepted for the same tender — acceptance is recorded first, then the count is reconciled. A handler that treats tender.accepted as "all pieces confirmed" is wrong.discrepancy.type is short or over; delta is signed. shipment_ids lists the whole tender, not just the missing pieces — which ones are missing is not known at this point.{
"object": "webhook",
"eventID": "evt_972bcb",
"event": "tender.discrepancy",
"type": "tender.discrepancy",
"identifiers": {
"tender": "tnd_4Km2Xp",
"facility": "lms_sort_dfw",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "tender.discrepancy",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Tender tnd_4Km2Xp accepted short",
"lat": "32.878917",
"lon": "-96.960446",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "accepted"
},
"eventSpecific": {
"carrier": "GoFo",
"handoff": "inject",
"pieceCount": 2,
"pieceCountReceived": 1,
"shipmentCount": 2,
"acceptedBy": "R. Alvarez",
"acceptedAt": "2026-08-06T14:22:00Z",
"discrepancyType": "short",
"expectedPieceCount": 2,
"receivedPieceCount": 1,
"delta": -1
}
}A store's daily manifest was finalized.
Fires whenPOST /v2/manifests/{id}/close
manifest_id, not id. This is the only event payload in the catalog that does not carry an id.{
"object": "webhook",
"eventID": "evt_82c4d2",
"event": "manifest.closed",
"type": "manifest.closed",
"identifiers": {
"manifest": "mfst_6Yn1Bd",
"store": "ph_store_4821",
"customer": "Acme Returns",
"partner": "acme_returns",
"webhook": "whk_A7fQ2m"
},
"event_info": {
"event": "manifest.closed",
"date": "2026-08-09",
"eventDateTime": "2026-08-09T16:04:00Z",
"summary": "Manifest mfst_6Yn1Bd closed",
"lat": "32.878879",
"lon": "-96.960447",
"coordinateSource": "device",
"scannedDateTime": "2026-08-09T16:03:52Z",
"status": "closed"
},
"eventSpecific": {
"packageCount": 2,
"manifestDate": "2026-08-06",
"closedAt": "2026-08-06T23:59:00Z"
}
}