Notifications
Webhooks push parcel updates to your systems, so you do not have to poll. You
register a callback URL per event type, we POST to it when the event happens.
The request and response shapes live in the API reference (POST /v1/webhooks,
GET /v1/webhooks, DELETE /v1/webhooks/{hook}). This page covers what the
reference cannot tell you: how delivery behaves, how to secure your endpoint,
and what to do when nothing arrives.
Subscribing
Two fields are required, and their names matter:
{
"callback_url": "https://example.com/webhooks/cabify",
"hook": "parcel"
}
The fields arecallback_urlandhookUnknown fields are ignored rather than rejected, so a request that spells
them any other way arrives with no callback URL and answers400 callback_url is required. If you get that while believing you sent a URL, check the field
names before anything else.
The callback_url has to be reachable from outside: we reject anything that
is not an http/https URL with a public host, including localhost, private
addresses such as 10.0.0.5 and internal names. The subscription fails with
callback_url must be an http(s) URL with a public host.
That is worth knowing before you start rather than after, because it is the
first thing you hit when developing locally. A tunnel to your machine works for
testing, and is the usual way to receive events on a laptop.
You can subscribe to three event types, one subscription each:
hook | When it fires |
|---|---|
parcel | Every parcel status change. |
parcelLocation | The driver's location is updated. |
proofCodeGenerated | A proof of delivery code is generated for a parcel. |
Two optional fields:
headers— headers we include in every notification we send you. See
Securing your endpoint below.timeout— how long we wait for your endpoint to answer, in
milliseconds.
GET /v1/webhooks returns what is registered for your account. Use it before
opening a support ticket: most "we are not receiving anything" reports turn out
to be a subscription that was never created, or created against a different set
of credentials.
Securing your endpoint
Your callback URL is public, so you need to verify that a request really came
from us. There are two supported ways:
- A custom header. Register it in
headerswhen you subscribe and we send
it with every notification. Usually anAuthorizationheader with a token
you generated and can check. - OAuth client credentials. Contact us and we will set up a
clientIdand
clientSecretfor the callbacks.
There is no HMAC signature on these webhooks
parcel,parcelLocationandproofCodeGeneratedare not signed. There
is no signature header to validate and no signing key. If you have read a
general Cabify webhook guide that describes HMAC-SHA256, it does not apply to
Logistics. Authenticate with a custom header or OAuth, as above.
A header value of exactly {parcelID} is replaced with the id of the parcel the
notification is about. It lets you pass the parcel id in a header as well as in
the body, if your receiving stack needs that.
Delivery policy
What we consider a success. Any 2xx or 3xx response. A 4xx or 5xx is
a failure. Answer quickly with a 200 and do your processing asynchronously —
if you do the work before replying, you are spending your own timeout budget.
Timeouts. We wait for the timeout you registered with the subscription. If
you did not set one, do not assume it is generous; set it deliberately.
Retries. Each event is sent once per attempt, and redelivery is driven by
our internal event bus rather than by a retry loop inside the request. That has
one consequence worth designing for:
Events expireAn event that cannot be delivered for long enough is eventually discarded and
never arrives. In production that window is several hours. If your endpoint is
down for a whole morning, the updates from that morning are gone — they will
not be replayed when you come back up.
There is no manual replay. We cannot re-send yesterday's notifications on
request. Recovery is reconciliation, not replay: see below.
Duplicates and ordering. Assume both. The same event may arrive more than
once, and events may arrive out of order — a delivered can land before the
delivering that preceded it. Make your handler idempotent, keyed on the parcel
id plus the state, and treat the state in your own database as "the latest one I
have seen", not "the last one that arrived".
Reconciliation
Because events expire and there is no replay, webhooks alone are not a complete
record. Anything that must be correct — billing, SLA reporting, order state —
needs a reconciliation pass:
- Poll
GET /v1/parcels/{id}for parcels that have not reached a final state
within the time you expect. GET /v1/parcels/{id}/timelinegives the full state history, including
transitions whose notification you never received.- Run it on a schedule for anything still open. This is the only way to close
the gap left by an outage on your side.
If you use an agent with our MCP server, the
list_webhooks, get_parcel and track_parcel tools do exactly this against
your own account.
When nothing arrives
Work through it in this order:
- Is the subscription there?
GET /v1/webhooks, with the same credentials
your integration uses. A subscription belongs to the account that created it. - Is the URL the one you think? Compare it character by character with what
is registered, including the scheme and any path. - Is your endpoint answering
2xxquickly? A401from your side is the
most common cause: the custom header you registered no longer matches what
your service expects. A slow endpoint that times out looks the same as a
dead one from here. - Are you looking at the right environment? Sandbox and production are
independent, with separate subscriptions. - Has time passed? If your endpoint was down for hours, the events from
that period are expired, not pending. Reconcile, do not wait.
If all five check out, open a ticket with your client id, the callback URL and a
parcel id that should have produced a notification and did not.
Updated 2 days ago
