> For the complete documentation index, see [llms.txt](https://routelift.gitbook.io/routelift-api-integration/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://routelift.gitbook.io/routelift-api-integration/webhooks.md).

# Webhooks

## Webhooks

Webhooks allow you to receive real-time updates when certain events happen on RouteLift. For example, when the status of a delivery changes, we'll notify your system automatically.

### How It Works

1. You provide a webhook URL via your Partner Dashboard.
2. RouteLift sends an HTTP POST request to your URL when an event occurs.
3. Your server receives and processes the data.

Webhooks are only sent once your business is activated and a webhook URL is saved. If either is missing, events are skipped silently.

### Webhook Events

| Event                             | When it fires                                               |
| --------------------------------- | ----------------------------------------------------------- |
| `delivery.created`                | The delivery has been created and accepted into our system. |
| `delivery.assigned`               | A driver has been assigned. Includes the driver's details.  |
| `delivery.in_transit_to_pickup`   | The driver is on the way to the pickup location.            |
| `delivery.picked_up`              | The driver has collected the package.                       |
| `delivery.in_transit_to_delivery` | The package is on its way to the receiver.                  |
| `delivery.delivered`              | The package has been delivered.                             |
| `delivery.cancelled`              | The delivery was cancelled.                                 |
| `delivery.failed`                 | The delivery could not be completed.                        |

### Sample Payload

```json
{
  "event": "delivery.delivered",
  "timestamp": "2025-05-21T12:34:56Z",
  "eventId": "3b59132c-9281-4b9b-bf09-1b93a3e362cc",
  "data": {
    "trackingCode": "dlv_7890",
    "orderId": "AB6237QS",
    "price": 500
  }
}
```

| Field               | Description                                                                    |
| ------------------- | ------------------------------------------------------------------------------ |
| `event`             | The event name, from the table above.                                          |
| `timestamp`         | When the event occurred.                                                       |
| `eventId`           | Unique identifier for the event. Use it to prevent duplicate webhook handling. |
| `data.trackingCode` | The delivery's tracking code.                                                  |
| `data.orderId`      | The id you sent when creating the delivery request.                            |
| `data.price`        | The delivery price.                                                            |

#### delivery.assigned

`delivery.assigned` carries an additional `driver` object inside `data`:

```json
{
  "event": "delivery.assigned",
  "timestamp": "2025-05-21T12:34:56Z",
  "eventId": "3b59132c-9281-4b9b-bf09-1b93a3e362cc",
  "data": {
    "trackingCode": "dlv_7890",
    "orderId": "AB6237QS",
    "price": 500,
    "driver": {
      "name": "John Doe",
      "phone": "2348012345678",
      "photo": "https://photo.com",
      "vehicleType": "motorcycle",
      "vehiclePlate": "ABC123"
    }
  }
}
```

### Events We Do Not Send

A delivery that has been assigned can return to an unassigned state. A driver may decline it, or our operations team may release it so that another driver can pick it up.

**RouteLift does not send a webhook when this happens.** The delivery returns to a pending state internally and is offered to other drivers. You will receive `delivery.assigned` again once a new driver takes it, carrying a different `driver` object.

> `delivery.assigned` is not final. Do not treat the driver in that payload as fixed for the lifetime of the delivery, and do not treat the absence of further events as confirmation that the original driver is still assigned. If you need certainty about the current state of a delivery, poll the tracking endpoint.

### Securing Webhooks

To verify that webhook requests are from RouteLift:

* We include an `X-Signature` header on every request.
* The value is an HMAC SHA256 hash of the raw request body, hex encoded, signed with your API secret key.
* Compute the HMAC over the exact bytes of the request body, before any JSON parsing or re-serialisation.

> The header value is the hex digest on its own.

Example:

```
X-Signature: 3d8f3f204a1d9c2b7e5a8f1c6d0b4e93a7f2c85d1e6b09a4f3c7d2e8b5a1f6c0d
```

Node.js example:

```js
const crypto = require("crypto");

function isFromRouteLift(rawBody, signatureHeader, apiSecretKey) {
  const expected = crypto
    .createHmac("sha256", apiSecretKey)
    .update(rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
```

### Handling Failures

* We expect a `2xx` response from your server.
* Requests time out after 60 seconds.
* If your server responds with a `4xx` or `5xx` status, or times out, we retry up to 3 times in total, with exponential backoff starting at 2 minutes.
* After the final attempt the event is dropped and is not retried again.

Because a delivery can be retried and because some actions emit more than one event, your endpoint should be idempotent. Use `eventId` to discard events you have already processed.

### Setup Instructions

1. Go to **Settings → API Keys & Webhooks**
2. Enter your webhook URL
3. Copy your API secret key — this is the key used to verify signatures

### Testing Webhooks (Sandbox Only)

These endpoints are available in sandbox/development environments only. They let you simulate the full delivery lifecycle so you can test your webhook integration end-to-end without a real driver.

> These endpoints require authentication. Include your API key as a Bearer token in the `Authorization` header. They are not available in production environments and will return `404` there.

#### Simulate Driver Assignment

Assigns a test driver to a delivery and triggers the `delivery.assigned` webhook event.

**Endpoint**

```
POST /v1/deliveries/simulate/assign-driver
```

**Example Request**

```json
{
  "trackingCode": "dlv_7890"
}
```

**Example Response**

```json
{
  "status": true,
  "message": "Driver assigned successfully",
  "data": {
    "trackingCode": "dlv_7890",
    "orderId": "abc",
    "price": 5000,
    "driver": {
      "name": "John Doe",
      "photo": "https://photo.com",
      "vehicleType": "motorcycle",
      "vehiclePlate": "ABC",
      "phone": "1234567890"
    }
  }
}
```

#### Simulate Delivery Event

Triggers a specific delivery lifecycle event on a delivery, firing the corresponding webhook to your configured URL.

**Endpoint**

```
POST /v1/deliveries/simulate/delivery-event
```

**Event → Webhook Mapping**

| `event` value          | Webhook(s) fired                                               |
| ---------------------- | -------------------------------------------------------------- |
| `ON_ROUTE_TO_PICKUP`   | `delivery.in_transit_to_pickup`                                |
| `PACKAGE_PICKED_UP`    | `delivery.picked_up` **and** `delivery.in_transit_to_delivery` |
| `ON_ROUTE_TO_DELIVERY` | `delivery.in_transit_to_delivery`                              |
| `PACKAGE_DELIVERED`    | `delivery.delivered`                                           |

> `PACKAGE_PICKED_UP` emits two webhooks, and `ON_ROUTE_TO_DELIVERY` emits `delivery.in_transit_to_delivery` again. Following the sequence below, you will receive `delivery.in_transit_to_delivery` twice with different `eventId` values. This mirrors production behaviour — deduplicate on `eventId`.

**Example Request**

```json
{
  "trackingCode": "dlv_7890",
  "event": "PACKAGE_PICKED_UP"
}
```

**Example Response**

```json
{
  "status": true,
  "message": "Delivery event 'PACKAGE_PICKED_UP' triggered successfully",
  "data": {
    "trackingCode": "dlv_7890",
    "event": "PACKAGE_PICKED_UP"
  }
}
```

#### Recommended test sequence

1. Create a delivery via `POST /v1/deliveries`
2. Call `simulate/assign-driver` → verify `delivery.assigned`
3. Call `simulate/delivery-event` with `ON_ROUTE_TO_PICKUP` → verify `delivery.in_transit_to_pickup`
4. Call `simulate/delivery-event` with `PACKAGE_PICKED_UP` → verify `delivery.picked_up` and `delivery.in_transit_to_delivery`
5. Call `simulate/delivery-event` with `ON_ROUTE_TO_DELIVERY` → verify `delivery.in_transit_to_delivery`
6. Call `simulate/delivery-event` with `PACKAGE_DELIVERED` → verify `delivery.delivered`

Each simulated step advances the delivery's real state, so run them in order. Calling a step twice returns an error because the delivery has already moved past it.
