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.