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

# Webhooks

Webhooks send **real-time shipment updates** from Dispatch to your server. When a shipment’s status changes (for example driver assigned, picked up, delivered), Dispatch `POST`s a JSON payload to the URL you register.

You do **not** need to poll the API for every status change if you implement webhooks correctly.

***

## Register a webhook (Merchant Dashboard)

1. Sign in at <https://merchants.staging.dispattch.dev>
2. Open **Webhooks** in the sidebar.
3. Click **Register Webhook**.
4. Enter your **Target URL** — must be **HTTPS** and reachable from the public internet.
5. Click **Save Webhook**.
6. On the success screen, copy the **Signing Key** and click **I understand**.

> **Important:** The signing key is shown **only once**. If you lose it, delete the webhook in the dashboard and register again to receive a new key. Update your server configuration with the new key.

Your dashboard lists registered URLs and whether they are active. You can delete a webhook if you no longer want events at that URL.

***

## What your endpoint must do

Your server must expose a URL that:

| Requirement | Details                                                                                                          |
| ----------- | ---------------------------------------------------------------------------------------------------------------- |
| Protocol    | **HTTPS** only (not plain `http://`, except via a tunnel in development)                                         |
| Method      | Accept **`POST`**                                                                                                |
| Body        | JSON shipment object (see below)                                                                                 |
| Response    | Return HTTP status **below 400** (for example `200` or `204`) **quickly** after you have safely stored the event |

If your endpoint returns **4xx or 5xx**, Dispatch treats the delivery as failed. Failed deliveries are **not automatically retried**, so build your handler to be reliable and monitor for missing updates.

***

## Verify requests are from Dispatch

Every webhook request includes:

```http
Content-Type: application/json
X-Dispatch-Signature: sha256=<hex_digest>
```

The signature is **HMAC-SHA256** of the **raw request body** using your **signing key** (`dsp_wh_...`).

### Verification steps

1. Read the raw body as bytes (do not re-serialize JSON before verifying).
2. Compute `HMAC_SHA256(signing_key, raw_body)`.
3. Hex-encode the result and prefix with `sha256=`.
4. Compare to `X-Dispatch-Signature` using a **constant-time** comparison.
5. If verification fails, respond with `401` or `403` and do not process the payload.

### Node.js example

```js
import crypto from "crypto";

export function verifyDispatchSignature(rawBody, signatureHeader, signingKey) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", signingKey).update(rawBody).digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || "");

  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}
```

### Python example

```python
import hmac
import hashlib

def verify_dispatch_signature(raw_body: bytes, signature_header: str, signing_key: str) -> bool:
    digest = hmac.new(
        signing_key.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    expected = f"sha256={digest}"
    return hmac.compare_digest(expected, signature_header or "")
```

Store the signing key in the same way as your API key (environment variable / secret manager).

***

## Payload format

The body is a **full shipment snapshot** (not a partial diff). You receive an updated object when shipment state changes—for example after a driver is assigned, pickup, delivery, cancellation, or failure.

Example payload:

```json
{
  "id": 4,
  "code": "SHP-1T74-F4E8",
  "merchant_id": 1,
  "status": "assigned_to_driver",
  "courier_company_id": 12,
  "assigned_driver_id": 7,
  "description": "Order #10042",
  "weight_kg": 3.5,
  "total_fee": 119.25,
  "start_address": "9.0123, 38.7465",
  "end_address": "9.0500, 38.8000",
  "assigned_to_driver_at": "2026-03-13T05:00:00+03:00",
  "created_at": "2026-03-13T04:20:26.0731704+03:00"
}
```

### Fields you will use most often

| Field                | Description                                                                      |
| -------------------- | -------------------------------------------------------------------------------- |
| `code`               | Unique shipment code—use as your idempotency key                                 |
| `status`             | Current lifecycle state (see [Reference](/integrate-with-dispatch/reference.md)) |
| `merchant_id`        | Your merchant account ID                                                         |
| `courier_company_id` | Assigned courier company                                                         |
| `assigned_driver_id` | Driver ID when assigned (may be null)                                            |
| `*_at` timestamps    | When key events occurred (`delivered_at`, `cancelled_at`, etc.)                  |

### Handle updates safely (idempotency)

* Save the event, then return `200` quickly; process heavy work asynchronously.
* Upsert your database row by `code` (and optionally compare timestamps or status).
* Duplicate deliveries can happen—treat repeated `status` values as no-ops.

***

## Test your webhook locally

1. Run a small HTTP server on your machine that implements verification and logging.
2. Expose it with an HTTPS tunnel (for example [ngrok](https://ngrok.com/)):\
   `https://abc123.ngrok-free.app/webhooks/dispatch`
3. Register that HTTPS URL in the Merchant Dashboard (**Webhooks** → **Register Webhook**).
4. Create or update a shipment in staging (via [API Keys](/integrate-with-dispatch/authentication-api-keys.md) or the dashboard) and progress its status.
5. Confirm your logs show the JSON body and `X-Dispatch-Signature` validates.

For a quick smoke test without your own server, you can temporarily use a request inspector (for example webhook.site)—remember to replace it with your real endpoint before production.

***

## Troubleshooting

| Problem                     | Likely cause                                                                           |
| --------------------------- | -------------------------------------------------------------------------------------- |
| No webhooks received        | URL not HTTPS, firewall blocking Dispatch, or wrong URL registered                     |
| Signature always fails      | Parsing JSON before verify (use raw body), wrong signing key, missing `sha256=` prefix |
| Intermittent failures       | Endpoint too slow or returning 5xx under load—respond fast after persist               |
| Lost signing key            | Delete webhook and register again; update your server secret                           |
| Duplicate events in your DB | Missing idempotency on `code` / `status`                                               |

***

## Advanced: register via API (optional)

Most integrators register webhooks in the **Merchant Dashboard**. Registration via `POST https://service.staging.dispattch.dev/api/v1/webhooks` with a JSON body `{"url":"https://..."}` requires **dashboard login (Bearer token)**, not an API key—the response includes `signing_key` and `webhook_id` the same way as the UI.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://dispatch-2.gitbook.io/integrate-with-dispatch/webhooks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
