> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plato.ae/llms.txt
> Use this file to discover all available pages before exploring further.

# Order Update Webhook

> Send an aggregator order status update into Plato POS and KDS.

Call this webhook when the status of an order previously sent to Plato changes. Plato finds the order using `type` and `externalOrderId`, updates its status, and broadcasts the change to connected POS and KDS devices.

The API key role needs `restaurant:order:changeStatus`.

## Request

```bash theme={null}
curl "https://api.plato.ae/api/v1/aggregators/orders/updates" \
  -X POST \
  -H "Authorization: Bearer $PLATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: talabat-update-84721" \
  -d '{
    "type": "talabat",
    "externalOrderId": "TLB-84721",
    "status": "CANCELLED",
    "reason": "Customer cancelled through Talabat",
    "updatedAt": "2026-07-28T16:38:00+04:00"
  }'
```

## Fields

| Field             | Type     | Required | Notes                                                                  |
| ----------------- | -------- | -------- | ---------------------------------------------------------------------- |
| `type`            | string   | Yes      | Use the same value sent when the order was created, such as `talabat`. |
| `externalOrderId` | string   | Yes      | The aggregator order id used during creation.                          |
| `status`          | enum     | Yes      | The new Plato order status. See [statuses](#statuses).                 |
| `reason`          | string   | No       | Human-readable explanation, especially for cancellation or voiding.    |
| `updatedAt`       | ISO 8601 | No       | When the status changed in the aggregator, including a UTC offset.     |

Do not send `appSlug`. Plato resolves `type: "talabat"` to `type: "PICK_UP"` and `appSlug: "talabat"` using the same rules as order creation.

## Statuses

The webhook accepts:

* `PENDING`
* `ACCEPTED`
* `PROCESSING`
* `READY`
* `SHIPPED`
* `DELIVERED`
* `COMPLETED`
* `CANCELLED`
* `VOIDED`

## Response

A successful update returns `200 OK`:

```json theme={null}
{
  "data": {
    "id": "cm_order_123",
    "externalOrderId": "TLB-84721",
    "referenceNumber": 1043,
    "appSlug": "talabat",
    "type": "PICK_UP",
    "previousStatus": "PENDING",
    "status": "CANCELLED",
    "updatedAt": "2026-07-28T12:38:01.120Z"
  },
  "meta": {
    "idempotentReplay": false,
    "requestId": "talabat-update-84721"
  }
}
```

Sending the current status again is safe. Plato returns `200 OK` with `meta.idempotentReplay: true`.

## Errors

| Status | Code                                 | Meaning                                                      |
| ------ | ------------------------------------ | ------------------------------------------------------------ |
| `401`  | `MISSING_API_KEY`, `INVALID_API_KEY` | The API key is missing or invalid.                           |
| `403`  | `INSUFFICIENT_PERMISSION`            | The key needs `restaurant:order:changeStatus`.               |
| `404`  | `ORDER_NOT_FOUND`                    | No order matches the submitted `type` and `externalOrderId`. |
| `409`  | `AGGREGATOR_APP_NOT_INSTALLED`       | The aggregator app is not installed in the key workspace.    |
| `422`  | `VALIDATION_ERROR`                   | A field or status is invalid.                                |

Use the same `type` value for creation and every later update. This keeps order matching deterministic, including supported aliases such as `noon-food` resolving to `noon_food`.
