Receiving webhooks
A webhook has us call your server when one of your shipments changes, so you don't have to keep asking. Set one up with Create a webhook: give us a target_url and the events you want. The answer includes a token. Keep it, because every event we send carries it.
The events
| Event | Sent when |
|---|---|
shipment.updated.status | A shipment's status changes to order_received, in_transit or delivered. Other statuses send nothing; see Order statuses. |
shipment.updated.waybill | A shipment's waybill number changes: first made, made again, or cleared (updated_value is then null). |
Subscribe to the exact strings above. A hub order has two shipments, so it sends events for each leg, under the same order.
What we send
A POST to your target_url, with Content-Type: application/json and this body:
| Field | What it is |
|---|---|
object_type | order. |
object_id | The order's id, the one Get an order takes. |
event_type | Which event this is. |
original_value | The value before the change. null for a first waybill. |
updated_value | The value after the change. |
updated_at_utc | When the shipment changed, in UTC, ISO 8601 with milliseconds. |
token | Your webhook's token. |
The body has no other fields, and no event id.
{
"object_type": "order",
"object_id": 48213,
"event_type": "shipment.updated.status",
"original_value": "awaiting_pickup",
"updated_value": "in_transit",
"updated_at_utc": "2026-11-11T09:30:12.345Z",
"token": "q7Vt2mX9pL4sKe8RzW1nYg"
}
Checking it came from us
Compare the body's token with the token you kept from Create a webhook, and ignore any request where they differ. The token travels in the body, not in a header, and there is no signature. Use an https address so the token can't be read on the way.
The token never changes. To replace it, delete the webhook and create a new one.
Answering
Answer any 2xx status, as soon as you can. We ignore what you send back. Store the event and do the work afterwards, because we give up waiting after 60 seconds and count it as a failure.
Use the address you registered as it is. If it redirects with 301, 302 or 303, the event arrives at the new address as a GET with no body.
When your server doesn't answer
- Retries: we try five times in all, waiting longer each time, and stop after about five minutes. Anything other than a
2xxcounts as a failure, and so does no answer in 60 seconds. - Failing: if all five tries fail, the webhook's
statusbecomesfailingand it gets no more events. We email your company's email address to say so. - Missed events aren't sent again. Events from while a webhook was failing are lost.
- Getting it working again: the API can't switch a failing webhook back on. Delete it and create a new one, which gives you a new token.
Because events can be lost, check your open orders now and then with List orders, and don't rely on webhooks alone.
Duplicates and order
- The same event can arrive twice, for example if your server answered slowly and we tried again. To spot a repeat, compare
object_id,event_type,updated_valueandupdated_at_utc. - Events can arrive out of order. Each one is sent on its own, and a retried event arrives after later ones. Use
updated_at_utcto tell which is newer. - Read the order when an event arrives. Treat the event as a prompt: call Get an order for the current state. Compare
shipments[].statusandshipments[].waybill, not the order's ownstatusandwaybill: on a hub order those belong to the last shipment, and the event doesn't say which leg changed. An event can reach you a moment before the change shows there, so if the order still shows the old value, wait a few seconds and read it again.
Testing the address
Our team can test your address from the portal, and that sends an empty POST. Answer it with a 2xx too.