Connect your own system: orders API and webhooks
For: Owners, Operations
Where: Setup > Integrations > Webhooks, API keys
Available: Web dashboard · All countries
This guide is for the developer who connects a restaurant's own system to Zyda, such as an ERP, a POS or a spreadsheet
tool. It covers both directions: Zyda sending orders to your system, and your system sending orders to Zyda.
How it works
- Webhooks (Zyda to your system). Zyda sends you an HTTP request when one of the store's orders changes.
- Orders API (your system to Zyda). You send Zyda the orders the store took outside Zyda, for example on a marketplace
or on the phone.
You can use one direction, or both. Someone at the store who can open Setup > Integrations sets up both in the
dashboard. See Set up webhooks and API keys.
All examples on this page are made up. They contain no real key, secret, store or customer.
The API keys page, with the address your system sends orders to
Zyda's webhooks, which send your orders to your system as they happen, have their own guide: Receive Zyda orders by
webhook.
Get an API key
1. In the Zyda dashboard, open Setup > Integrations > API keys.
2. Select Create key, then Create.
3. Copy the key. Zyda shows it once. Keep it on your server only.
A key starts with zyda_sk_. The key names the store, so you never send a store id. The store can revoke a key at any
time. A store can have up to 10 active keys.
Send an order
POST https://dashboard.zyda.com/api/v1/stores/orders
Authorization: Bearer <key>
Content-Type: application/json
- Send one order per request, as a JSON object.
- The body can be at most 256 KB.
- One key can make at most 120 requests a minute. Over that, the answer is 429 with a Retry-After header in seconds.
What Zyda does with the order depends on status:
- completed or canceled: a recorded sale. Zyda stores the sale. Nothing else happens: no message to the customer, no
kitchen ticket, no POS push, no webhook event.
- placed: a live order. The order is still open. It goes to the store's current orders, and staff move it through its
statuses. This works only for an order source with Show in current orders turned on. See "Live orders".
Order fields
Fields marked required must be present and not blank. Zyda ignores fields that are not listed here.
| Field | Type | Required | Notes | |---|---|---|---| | branch_id | integer | required | Zyda's id of the branch. It
must be a branch of the store that owns the key. | | channel | string | required | The order source. One of the keys in
"Order sources" below. The store must have it turned on. | | external_order_id | string | required | Your own id for the
order. At most 128 characters. Letter case is ignored, so ERP-1 and erp-1 are the same order. | | placed_at | string |
required | When the customer placed the order. ISO 8601 with a time zone, for example 2026-10-14T13:05:00+03:00. At
most 10 minutes in the future. | | status | string | required | completed, canceled or placed. | | total | number |
required | What the customer paid in total. See "Money". | | currency | string | required | The store's currency code,
for example KWD or EGP. Any other currency is refused. | | subtotal | number | optional | See "Money". | | discount |
number | optional | See "Money". | | delivery_fee | number | optional | See "Money". | | tax | number | optional | See
"Money". | | delivery_type | string | optional | delivery, pickup or dine_in. | | payment_method | string | optional |
cash, card_on_delivery or prepaid. | | notes | string | optional | Free text. At most 2000 characters. On a live order,
staff see the first 1000 characters as the order note. The customer never sees it. | | customer | object | optional |
See "The customer object". null is accepted. | | items | array | optional | The order lines. At most 200. See "The item
object". |
The customer object
| Field | Type | Required | Notes | |---|---|---|---| | name | string | optional | At most 255 characters. | | phone |
string | optional | At most 32 characters. Required for a live order. |
Zyda finds the customer by phone only. A local number is read in the store's country, or send the country code
(+965...). An invalid phone links no customer, but a recorded sale is still saved.
The item object
Each line in items:
| Field | Type | Required | Notes | |---|---|---|---| | name | string | required | At most 255 characters. | | quantity
| number | required | More than 0 and less than 100,000. Zyda keeps 3 decimals, so 1.5 kg is 1.5. Must be a whole number
on a live order. | | unit_price | number | required | The price of one unit. See "Money". | | external_item_id | string
| optional | Your own id for the product. At most 128 characters. | | zyda_item_id | integer | optional | Zyda's id of
the product variant. At most 2,147,483,647. Required on every line of a live order. | | modifiers | array | optional |
Options on the line. At most 50. See "The modifier object". |
The modifier object
Each entry in modifiers:
| Field | Type | Required | Notes | |---|---|---|---| | name | string | required | At most 255 characters. | | quantity
| number | optional | Left out, null or blank means 1. | | unit_price | number | optional | Left out, null or blank
means 0. | | external_id | string | optional | Your own id for the option. At most 128 characters. |
Order sources
The value of channel is one of these keys:
website, app, whatsapp, instagram, facebook, tiktok, phone, walk_in, talabat, instashop, deliveroo, keeta, jahez,
hungerstation, careem, noon_food, elmenus, breadfast, snoonu, mrsool, the_chefz, rafeeq, other
The store turns sources on and off in Setup > Business > Order sources. A source that is turned off is refused for a new
order. A resend or a cancel of an order Zyda already holds is still accepted after the store turns its source off.
Money
Every amount is in the store's currency, from 0 up to less than 1,000,000,000, with 3 decimals kept. Send a JSON number
or a string of digits. A string such as "12.750" avoids rounding errors.
Send the same order again
channel plus external_order_id names one order of the store. Sending it again never makes a second order. So it is safe
to retry a request after a timeout.
Sending a recorded sale again updates it (200, "result": "updated"). Required fields are replaced. An optional field you
leave out keeps its old value. items, when sent, replaces all lines. To cancel it, send it again with "status":
"canceled". It cannot be sent again as placed.
If two requests for the same order arrive at the same time, one of them gets 409. Send it again after a few seconds.
Live orders
An order with "status": "placed" becomes a live order when its source has Show in current orders turned on (Setup >
Business > Order sources). It reaches the store's current orders like an order a staff member keys in. On any other
source, placed is refused with 422. Send the order once it is completed instead.
A live order sent with the API:
- sends the customer no message;
- pays no Zyda order commission;
- earns the customer no loyalty points or cashback;
- sends no purchase event to the store's ad platforms;
- still goes to the store's POS, alerts the staff and is accepted automatically if the store accepts orders
automatically.
Version 1 limits. Zyda refuses each one with 422 and its own message:
- delivery_type must be pickup, or left out. Version 1 takes no delivery address.
- items is required.
- Every line needs zyda_item_id, the Zyda id of the product variant.
- The product must be published at the branch and allow pickup. It must not be a combo, and it must not need options
chosen.
- Every line's quantity is a whole number. The whole order holds at most 9,999 units.
- customer.phone is required and must be a valid phone number.
- payment_method is cash, card_on_delivery or prepaid. Left out, it is cash. prepaid means the money is already taken.
Zyda takes no payment.
Zyda's menu prices the order, and the answer carries Zyda's total. Staff see modifiers as text on the line.
The store can still refuse the order, for example when the branch is closed or an item is out of stock. The answer is
then 422. The reason is in errors.order, and its code is in code. No order is made.
After the order is placed, staff move it in Zyda. Follow its status with webhooks. To cancel it, send it again with
"status": "canceled" (200, "result": "canceled"; Zyda never refunds; 409 if the store can no longer cancel it). Any
other resend changes nothing (200, "result": "unchanged").
Answers
Every answer is JSON.
A success answer holds Zyda's id, the result, your channel and external_order_id, and the order's status in Zyda. kind
is imported_order for a recorded sale and order for a live order. customer_linked is true when Zyda found or made the
customer from the phone. A live order also has order_number and Zyda's total.
| Code | Meaning | |---|---| | 201 | The order is new and Zyda saved it. "result": "created". | | 200 | Zyda already had
this order. updated: a recorded sale took the new values. unchanged: a live order sent again stays as it is. canceled:
you canceled a live order. | | 400 | The body is not a JSON object. | | 401 | The key is missing, unknown or revoked. |
| 409 | Another request is writing the same order right now, or the store can no longer cancel this live order. | | 413
| The body is larger than 256 KB. | | 422 | A field is missing or wrong, or the store refused a live order (see
errors.order). | | 429 | More than 120 requests in a minute for this key. Wait the seconds in Retry-After. | | 503 |
Zyda cannot take the order right now. Send it again later. |
Every error answer has an error message. A 422 also has errors. It maps each field name to one or more messages. Line
fields are named by position, for example items[2].quantity:
{
"error": "invalid order",
"errors": {
"placed_at": ["is required"],
"items[0].quantity": ["must be greater than 0"]
}
}
Retry 409, 429, 503 and network errors with the same body. Do not retry 400, 401, 413 or 422 until you change the
request.
Example: a recorded sale
Request body:
{
"branch_id": 101,
"channel": "instashop",
"external_order_id": "ERP-100045",
"placed_at": "2026-10-14T13:05:00+03:00",
"status": "completed",
"total": "12.750",
"currency": "KWD",
"delivery_fee": "0.750",
"delivery_type": "delivery",
"payment_method": "prepaid",
"customer": { "name": "Sample Customer", "phone": "+96550000000" },
"items": [
{
"name": "Ribeye steak",
"quantity": "1.5",
"unit_price": "8.000",
"external_item_id": "SKU-RIB",
"modifiers": [{ "name": "Sliced thin", "external_id": "MOD-SLICE" }]
}
]
}
Answer 201:
{
"id": 5012,
"result": "created",
"kind": "imported_order",
"channel": "instashop",
"external_order_id": "ERP-100045",
"status": "completed",
"customer_linked": true
}
Example: a live order
Request (the source whatsapp has Show in current orders turned on):
{
"branch_id": 101,
"channel": "whatsapp",
"external_order_id": "ERP-100046",
"placed_at": "2026-10-14T13:20:00+03:00",
"status": "placed",
"total": "9.500",
"currency": "KWD",
"delivery_type": "pickup",
"payment_method": "cash",
"customer": { "name": "Sample Customer", "phone": "+96550000000" },
"items": [
{
"name": "Burger patties, 6 pieces",
"quantity": 2,
"unit_price": "4.750",
"zyda_item_id": 123456,
"modifiers": [{ "name": "Extra fat" }]
}
]
}
Answer 201:
{
"id": 880123,
"result": "created",
"kind": "order",
"order_number": "1042",
"channel": "whatsapp",
"external_order_id": "ERP-100046",
"status": "submitted",
"customer_linked": true,
"total": 9.5
}
Rules and limits
- Up to 5 webhooks and up to 10 active API keys per store.
- 120 requests a minute per key. Bodies up to 256 KB. Up to 200 lines per order and 50 modifiers per line.
- The delivery log keeps 30 days.
Common questions
Does Zyda message the customer about an order I send?
No. Neither a recorded sale nor a live order sends the customer anything.
Can a live order be a delivery order?
Not in version 1. A live order must be pickup. Send a delivery order once it is completed, as a recorded sale.
My webhook stopped. Why?
After 50 failed deliveries in a row, Zyda turns it off. Fix your endpoint, then turn Send events back on. The delivery
log shows each error.
Related articles
- Set up webhooks and API keys
- Choose the order sources your team records
- Send your orders to a Google Sheet