---
updatedAt: 2026-09-28T14:13:54.000Z
agentTools:
  projectIndex: https://cabify-api.readme.io/llms.txt
---

# 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:

```json
{
  "callback_url": "https://example.com/webhooks/cabify",
  "hook": "parcel"
}
```

> ❗️ The fields are `callback_url` and `hook`
>
> Unknown fields are ignored rather than rejected, so a request that spells
> them any other way arrives with no callback URL and answers `400 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:

1. **A custom header.** Register it in `headers` when you subscribe and we send
   it with every notification. Usually an `Authorization` header with a token
   you generated and can check.
2. **OAuth client credentials.** Contact us and we will set up a `clientId` and
   `clientSecret` for the callbacks.

> 🚧 There is no HMAC signature on these webhooks
>
> `parcel`, `parcelLocation` and `proofCodeGenerated` are **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 expire
>
> An 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}/timeline` gives 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](./logistics-api/mcp-server.md), 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:

1. **Is the subscription there?** `GET /v1/webhooks`, with the same credentials
   your integration uses. A subscription belongs to the account that created it.
2. **Is the URL the one you think?** Compare it character by character with what
   is registered, including the scheme and any path.
3. **Is your endpoint answering `2xx` quickly?** A `401` from 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.
4. **Are you looking at the right environment?** Sandbox and production are
   independent, with separate subscriptions.
5. **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.