Skip to content
Drive-Thru+
API v1

Developer API

Read and update orders and menus for one restaurant from your POS, kiosk or reporting tool, and receive signed webhooks the moment something changes. The restaurant owner creates the keys and webhooks in their workspace.

Base URL
https://www.dhawq.net/api/v1
Format
JSON. Money in minor units, names in English and Arabic, times in UTC.
Rate limit
120 requests per minute per key

Authentication

Send the API key as a bearer token on every request. A key belongs to one restaurant and only ever sees that restaurant’s data. Keep it on your server, never in an app or web page.

curl https://www.dhawq.net/api/v1/orders?status=received,preparing \
  -H "Authorization: Bearer <your key>" \
  -H "Accept: application/json"

Scopes

  • orders:read

    Read orders

    List orders and see their items, totals and status.

  • orders:write

    Update orders

    Mark orders preparing, ready or picked up, or cancel them.

  • menu:read

    Read the menu

    Branches, products, branch prices and availability.

  • menu:write

    Update availability

    Switch products on or off at a branch.

Endpoints

GET /orders orders:read

List orders

Newest first, 25 per page (up to 100). Follow next_cursor for the next page.

status
Comma-separated: pending_payment, scheduled, received, preparing, ready, picked_up, cancelled
branch
Branch code
since
ISO 8601 time; orders placed at or after it
per_page
1–100
cursor
next_cursor from the previous page
GET /orders/{id} orders:read

Get one order

The order with its items, totals, branch and times.

POST /orders/{id}/status orders:write

Move an order along

preparing, ready, picked_up (only when paid) or cancelled (with a reason). Same rules as the staff screens; a refused move answers 409.

{
    "status": "ready",
    "reason": null
}
GET /branches menu:read

List branches

Codes, bilingual names and locations.

GET /branches/{code}/menu menu:read

Branch menu right now

Categories and products with the branch price, availability and the reason when unavailable.

PUT /branches/{code}/products/{id}/availability menu:write

Switch a product on or off

available, out_of_stock_today (back by itself at local midnight) or not_served.

{
    "status": "out_of_stock_today"
}

Webhooks

Each webhook receives a POST with a JSON body: id (the same across retries, use it to ignore duplicates), type, created_at, restaurant_id and data. Answer with any 2xx status within 10 seconds.

  • order.created
  • order.status_changed
  • payment.succeeded
  • payment.refunded

Check every delivery: the X-DTP-Signature header holds the time and an HMAC-SHA256 of "time.body" made with your endpoint’s signing secret. Compare in constant time and reject deliveries older than 5 minutes.

// PHP: verify a delivery
[$t, $v1] = sscanf($_SERVER['HTTP_X_DTP_SIGNATURE'], 't=%d,v1=%s');
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
$valid = hash_equals($expected, $v1) && abs(time() - $t) <= 300;

// Node.js
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));

If your system doesn’t answer 2xx, we retry after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours. An endpoint that only fails for 3 days is switched off and the owner is told.

Errors & limits

Errors always have the same shape, with a stable code you can act on.

{ "error": { "code": "insufficient_scope", "message": "This key does not have the \"orders:write\" scope." } }
401
The key is missing, wrong or revoked.
403
The key doesn’t have the scope this endpoint needs.
404
Not found in this restaurant.
409
Not possible right now (for example an unpaid order can’t be picked up).
422
Invalid input; the errors say which field.
429
Too many requests; wait for the Retry-After seconds.