Skip to main content
View as Markdown

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​

EventSent when
shipment.updated.statusA shipment's status changes to order_received, in_transit or delivered. Other statuses send nothing; see Order statuses.
shipment.updated.waybillA 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:

FieldWhat it is
object_typeorder.
object_idThe order's id, the one Get an order takes.
event_typeWhich event this is.
original_valueThe value before the change. null for a first waybill.
updated_valueThe value after the change.
updated_at_utcWhen the shipment changed, in UTC, ISO 8601 with milliseconds.
tokenYour 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 2xx counts as a failure, and so does no answer in 60 seconds.
  • Failing: if all five tries fail, the webhook's status becomes failing and 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_value and updated_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_utc to 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[].status and shipments[].waybill, not the order's own status and waybill: 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.

API ExplorerSandbox

Pick a call on the right and run it. Every call goes to the sandbox, so nothing is really shipped or charged. The answer appears here.