The Orders API enables your system to receive, manage, and track customer orders coming from third-party marketplaces integrated with Kitchenhub. It supports both webhook-based and polling-based integration models.
๐งญ How It Works (Overview)
At a high level:
- Orders are received from marketplaces (e.g., DoorDash, Uber Eats, etc.).
- Kitchenhub processes and normalizes the data.
- Your system receives the order via a webhook notification or retrieves it via polling.
- You can respond to the order (accept, cancel, etc.) using the API.
๐ Webhook Notifications (Recommended)
Kitchenhub sends real-time notifications for new orders to a dedicated webhook endpoint.
How It Works:
- Configuration: In your Kitchenhub integration, configure your webhook URL to point to the endpoint on your server that will handle order notifications. The correct endpoint is:
POST /webhooks/order
- Delivery: When a new order is received, Kitchenhub sends a
POST
request to/webhooks/order
with the order payload. - Acknowledgment: Your server must respond with an HTTP 200 (OK) status to confirm receipt.
- Processing: Once acknowledged, the order can be managed via subsequent API calls.
Using webhooks ensures minimal delay for real-time order processing.
๐ฆ Order Payload Example
Below is an example payload that aligns with the specification:
{
"order_id": "123456",
"status": "new",
"store_id": "store_001",
"scheduled_time": null,
"customer": {
"name": "John Doe",
"phone": "+1234567890"
},
"items": [
{
"item_id": "burger_001",
"name": "Cheeseburger",
"quantity": 2,
"modifiers": [
{
"name": "Extra cheese"
}
]
}
],
"total_price": 19.99,
"delivery_type": "pickup",
"marketplace": "Doordash"
}
Notes:
scheduled_time
is a date-time string in ISO 8601 format ornull
.delivery_type
accepts values likepickup
ordelivery
.- All fields are required as per the OpenAPI specification.
๐ Order Status Lifecycle
The standard order statuses are as follows:
Status | Description |
---|---|
new | The order has been created and is awaiting processing. |
accepted | The order has been accepted; the preparation time has been set. |
completed | The order has been prepared and handed off to the customer or courier. |
cancelled | The order has been cancelled (by the customer, system, or store). |
Orders should be responded to within the time window defined by the provider (typically up to 5-15 minutes).
๐ฅ Managing Orders
Accept Order
-
Endpoint:
POST /orders/{order_id}/accept
-
Description: Accepts the order and sets the preparation time.
-
Request Payload:
{ "prep_time": 15 }where
prep_time
is the preparation time in minutes. -
Response: HTTP 200 OK upon successful acceptance.
Cancel Order
- Endpoint:
POST /orders/{order_id}/cancel
- Description: Cancels the order.
- Request Payload:
No payload is required (if needed, send an empty JSON object{}
). - Response: HTTP 200 OK upon successful cancellation.
Retrieve Orders (Polling)
If your system does not support webhooks, you can periodically poll for new orders:
- Endpoint:
GET /orders
- Description: Retrieves a list of orders in JSON format (an array of order objects).
Polling is an alternative method to retrieve orders when webhook integration is not available.
๐งช Testing
Kitchenhub provides a staging environment where you can validate and test your integration. In the staging environment you can:
-
Webhook Testing:
Validate webhook delivery by sending test orders to the/webhooks/order
endpoint. -
Order Management Testing:
Test your order acceptance and cancellation logic using the respective endpoints (POST /orders/{order_id}/accept
andPOST /orders/{order_id}/cancel
). -
Order Polling:
Monitor order status updates end-to-end by periodically retrieving orders viaGET /orders
. -
Mock Order Endpoint:
In addition to the above, Kitchenhub provides a mock order endpoint that allows you to simulate incoming orders without affecting live data. To create a test order, send aPOST
request to the mock order endpoint in the staging environment:POST /orders/mock
Example Test Order Payload:
{ "order_id": "test123", "status": "new", "store_id": "store_test", "scheduled_time": null, "customer": { "name": "Test User", "phone": "+1000000000" }, "items": [ { "item_id": "test_item_001", "name": "Test Burger", "quantity": 1, "modifiers": [ { "name": "No pickles" } ] } ], "total_price": 9.99, "delivery_type": "pickup", "marketplace": "TestMarket" }This test order is processed like a real order, allowing you to verify the end-to-end flow of order creation, processing, and management in your staging environment.
โ FAQ
Why do some orders arrive with a status other than new
?
Orders may already be in accepted
, completed
, or cancelled
status if they were processed on another device (e.g., a POS system or tablet connected to the same provider account). The webhook will always reflect the current state of the order.
Why I cannot accept an order?
If the provider is already connected to another POS, aggregator, or Kitchenhub restaurant, your integration can be in not main account (read only) mode. You wonโt be able to accept or cancel orders in that case.
Why canโt I cancel an order after accepting it?
Once an order has been accepted, it cannot be cancelled through our API. Some providers (like DoorDash) enforce this rule strictly.
Why is DoorDash cancelling my accepted orders?
If the order was accepted on a tablet and not acknowledged by your integration, DoorDash may cancel it via API. To prevent this, restaurants must disable or disconnect their DoorDash tablet when using Kitchenhub.
Why was the order cancelled before we accepted it?
If an order is not accepted within the providerโs timeout window, it will be automatically cancelled by the provider.
What happens if I ignore several orders?
If your integration repeatedly fails to accept orders, the provider may temporarily disable the store. In that case, it will need to be manually reactivated. We strongly recommend handling all orders promptly.
Why is customer contact info or address missing?
Some providers intentionally hide customer data (phone, email, or address) for privacy reasons. This is common with certain delivery partners.
Can I filter which orders are sent to my webhook?
Yes. You can configure webhook delivery filters by status, delivery type, or other attributes. Contact us to set this up.
Why doesnโt the total order price match the sum of items?
We do not calculate totals โ all pricing data comes directly from the provider. Minor differences may exist due to promotions, taxes, or rounding logic used by the marketplace.
Can item modifiers be nested?
Yes. Some providers support nested options (e.g., sauce inside a combo meal). Kitchenhub reflects these structures in the modifiers
field if available.
Why is the price calculation slightly different for each provider?
Each marketplace has its own tax, discount, fee, and rounding rules. Kitchenhub displays pricing exactly as received โ we do not normalize amounts.
Do all providers support scheduled orders?
No. Some providers do not send scheduled orders in advance. These orders are often delivered as ASAP orders shortly before prep time.
What is automatic order release for DoorDash?
We support an automatic order release mode for DoorDash to streamline high-volume operations. Contact our support team to learn how to enable it.
Why do some orders arrive with delay?
While most orders arrive in real time, some may be delayed by a few minutes depending on the providerโs infrastructure and delivery status updates.
What happens if item mapping fails?
If your menu is mapped with item IDs and we canโt match an incoming item, weโll return null
for partner_id
. Itโs up to you to decide how to handle such unmatched items (e.g., notify staff, flag the order, etc.).
When do driver details appear?
Driver details (name, phone, vehicle info) may not be available at the time of order creation. They are usually added later in a follow-up webhook update.
๐ Order Model
The Order object is fully detailed below, and weโve also included a sample instance here. This Order object will contain an ID field that youโll need later to confirm the order, so please keep track of it. All incoming orders will have a status of NEW.
The Order object always contains these top-level properties.
Property | Type | Nullable | Description |
---|---|---|---|
order | Object | No | Basic order info |
provider | Object | No | Provider info |
store | Object | No | Store info |
customer | Object | No | Customer info |
items | Array[Item] | No | List of order items |
delivery | Object | No | Delivery info |
payment | Object | No | Payment info |
charges | Object | No | Charges info |
timestamps | Object | No | Dates and times info |
order object
Property | Type | Nullable | Description |
---|---|---|---|
id | Integer | No | ID of order on KitchenHub side |
external_id | String | No | ID of order on the provider side |
number | String | No | Human readable order number |
daily_number | Integer | No | Serial order number for the current day |
type | String | No | The type of order, can be either pickup , delivery or dinein |
status | String | No | The status of order can be either new , accepted , completed or canceled |
notes | String | Yes | Customer notes. null if notes not set |
paid | Boolean | No | Whether the order paid or not |
asap | Boolean | No | Whether order ASAP or for future |
add_disposable_items | Boolean | Yes | Whether to add disposable items or not |
prep_time_minutes | Integer | Yes | Preparation time selected while accepting an order. null if the order was accepted using the provider's app |
provider object
Property | Type | Nullable | Description |
---|---|---|---|
id | String | No | Provider identifier. |
name | String | No | Human readable provider name |
merchant_id | String | Yes | Merchant ID on the provider side. |
store_id | String | Yes | Store ID on a provider side. |
location object
Property | Type | Nullable | Description |
---|---|---|---|
id | String | No | Location ID on KitchenHub side |
partner_location_id | String | Yes | Location ID on partner side. Can be set via API while creating a location or through KitchenHub web dashboard |
name | String | No | Location name on KitchenHub side |
street | String | Yes | Location street |
city | String | Yes | Location city |
state | String | Yes | Location state |
zipcode | String | Yes | Location zipcode |
lat | Float | Yes | Location latitude coordinate |
lon | Float | Yes | Location longitude coordinate |
store object
Property | Type | Nullable | Description |
---|---|---|---|
id | String | No | Store ID on KitchenHub side |
partner_store_id | String | Yes | Store ID on partner side. Can be set via API while creating store or through KitchenHub web dashboard |
name | String | No | Store name on KitchenHub side |
customer object
Property | Type | Nullable | Description |
---|---|---|---|
name | String | No | Customer name |
String | Yes | Customer email. null if email is not available on a provider side | |
phone_number | String | Yes | Customer phone number without country code. null if it is not available on a provider side |
phone_code | String | Yes | Additional phone code if a provider uses phone masking. null if it is not available on a provider side |
item object
Property | Type | Nullable | Description |
---|---|---|---|
id | String | Yes | ID of menu item on provider side |
partner_id | String | Yes | ID of menu item on your side |
name | String | No | Name of menu item on provider side |
quantity | Integer | No | Quantity of order item |
price | String | No | Price of order item (includes options prices) |
instructions | String | Yes | Customer notes for order item |
options | Array[Option] | No | List of options for order item |
option object
Property | Type | Nullable | Description |
---|---|---|---|
id | String | Yes | ID of menu option on provider side. null if item ID is not available in order data |
partner_id | String | Yes | ID of option on your side |
name | String | No | Name of menu option on provider side |
quantity | Integer | No | Quantity of item option |
price | String | No | Price of item option |
modifier_name | String | Yes | Name of menu modifier on provider side, for example: Choose size. null if modifier name is not available in order data |
delivery object
Property | Type | Nullable | Description |
---|---|---|---|
type | String | No | Indicates who is responsible for delivery - restaurant or provider |
status | String | Yes | Current delivery status. Can be either started , arriving , or delivered . null if order type is pickup or delivery by provider. |
notes | String | Yes | Delivery notes. null if delivery notes are not set |
address | Object | No | Delivery address info |
courier | Object | No | Courier info |
address object
Property | Type | Nullable | Description |
---|---|---|---|
country | String | Yes | Delivery country. null if order type is pickup |
street | String | Yes | Delivery street. null if order type is pickup |
city | String | Yes | Delivery city. null if order type is pickup |
state | String | Yes | Delivery state. null if order type is pickup |
zipcode | String | Yes | Delivery zip code. null if order type is pickup |
unit_number | String | Yes | Additional unit number. null if order type is pickup |
latitude | Float | Yes | Delivery latitude. null if order type is pickup or data not available |
longitude | Float | Yes | Delivery longitude. null if order type is pickup or data not available |
courier object
Property | Type | Nullable | Description |
---|---|---|---|
name | String | Yes | Courier name. null if order type is pickup or courier is not assigned yet |
photo_url | String | Yes | Courier photo. null if order type is pickup , courier is not assigned yet or this data is not available on the provider side |
phone_number | String | Yes | Courier phone number. null if order type is pickup , courier is not assigned yet or this data is not available on provider side |
status | String | Yes | Dispatching status. Can be either pending or assigned . null if order type is pickup |
payment object
Property | Type | Nullable | Description |
---|---|---|---|
method | String | No | Name of payment method used to pay for an order |
charges object
Property | Type | Nullable | Description |
---|---|---|---|
subtotal | String | No | The amount before any taxes, tips, discounts, or fees. |
tax | String | No | The amount of tax applied to the subtotal based on the tax rate. |
tips | String | Yes | The amount of gratuity or tips given to a courier. |
restaurant_tip_amount | String | Yes | The amount of tips that go directly to the restaurant. |
discount | String | Yes | The amount deducted from the subtotal due to promotions or offers. |
total | String | No | The final amount after applying taxes, tips, discounts, and fees. |
commission | String | Yes | The fixed fee charged by the platform or service provider (not for all providers). |
processing_fee | String | Yes | The fee charged for processing payments, usually based on transaction amount. |
adjustment | String | Yes | Any modifications to the order total, such as refunds or corrections. |
tax_payout | String | Yes | The amount of tax that needs to be paid to tax authorities by restaurant. Could be 0, if tax collected by a marketplace. |
delivery_fee | String | Yes | The fee charged for delivering the order to the customer. |
service_fee | String | Yes | A fee for using the platform or service to the customer. |
payout | String | Yes | The actual amount the restaurant receives after all fees and commissions. |
other_fee | Object | Yes | Any additional fees not categorized above, such as packaging or special service fees. |
timestamps object
All timestamps are in UTC formatted according to ISO8601 standard.
Property | Type | Nullable | Description |
---|---|---|---|
placed_at | String | No | date and time when the order was created on the provider side |
created_at | String | No | date and time when the order was created on the KitchenHub side |
accepted_at | String | Yes | date and time when the order was accepted on the KitchenHub side |
cancelled_at | String | Yes | date and time when the order was canceled on the KitchenHub side |
cancelled_by | String | Yes | |
completed_at | String | Yes | date and time when order was canceled on KitchenHub side |
pickup_at | String | No | date and time when the order should be ready for pickup by the customer or courier |
delivery_at | String | Yes | date and time when order should be delivered |
scheduled_for | String | Yes | date and time when order should be delivered if order is for future (asap==false ) |
๐ Order Example
{
"order": {
"id": 5501505922269184,
"external_id": "4ae18552-60a8-11ee-8553-abf2eec4f777",
"number": "201523913381757",
"daily_number": 15,
"type": "delivery",
"status": "completed",
"notes": "Contactless delivery, Please don't ring the doorbell.",
"paid": true,
"asap": false,
"add_disposable_items": true,
"prep_time_minutes": null,
"data": {
}
},
"provider": {
"id": "grubhub",
"name": "GrubHub",
"merchant_id": "2626147",
"store_id": "c212a250-3b69-45ab-9184-03fb2072x7xx"
},
"store": {
"id": "c212a250-3b69-45ab-9184-03fb2072x7xx",
"name": "The Spot Asian Food Hall",
"partner_store_id": null
},
"customer": {
"name": "Ari S",
"email": null,
"phone_number": "3012136310",
"phone_code": null
},
"items": [
{
"id": "6035887902",
"name": "2 Proteins",
"quantity": 1,
"price": "22.25",
"instructions": null,
"options": [
{
"id": "6035887894",
"name": "Sushi Rice",
"quantity": 1,
"price": "0.0",
"modifier_name": null
},
{
"id": "6035887878",
"name": "Mango",
"quantity": 1,
"price": "0.0",
"modifier_name": null
},
{
"id": "6035887903",
"name": "Seaweed Salad",
"quantity": 1,
"price": "0.0",
"modifier_name": null
}
]
},
{
"id": "6027293408",
"name": "Magic Roll",
"quantity": 1,
"price": "13.45",
"instructions": null,
"options": [
]
},
{
"id": "6027293408",
"name": "Magic Roll",
"quantity": 1,
"price": "13.45",
"instructions": null,
"options": [
]
},
{
"id": "6035887902",
"name": "2 Proteins",
"quantity": 1,
"price": "16.75",
"instructions": null,
"options": [
{
"id": "6035887894",
"name": "Sushi Rice",
"quantity": 1,
"price": "0.0",
"modifier_name": null
},
{
"id": "6035887867",
"name": "Avocado Egg Salad",
"quantity": 1,
"price": "0.0",
"modifier_name": null
},
{
"id": "6035887922",
"name": "Organic Tofu",
"quantity": 1,
"price": "0.0",
"modifier_name": null
},
{
"id": "6035887912",
"name": "Sesame Seed",
"quantity": 1,
"price": "0.0",
"modifier_name": null
}
]
}
],
"delivery": {
"type": "provider",
"address": {
"country": "US",
"street": "1234 Bettstrail Way",
"city": "Rockville",
"state": "MD",
"zipcode": "20854",
"unit_number": null,
"latitude": 39.07294,
"longitude": -77.17325
},
"courier": {
"name": "Donald V",
"photo_url": "https://s3.amazonaws.com/d5eb54503152.png",
"phone_number": "2407930399",
"status": "assigned"
},
"status": null,
"notes": null
},
"payment": {
"method": "CREDIT CARD"
},
"charges": {
"discount": "0.0",
"commission": null,
"processing_fee": null,
"adjustment": null,
"tax_payout": "0.0",
"restaurant_tip_amount": "0.0",
"delivery_fee": null,
"subtotal": "66.84",
"total": "72.44",
"tips": "0.0",
"service_fee": null,
"tax": "5.6",
"payout": null,
"other_fee": {}
},
"timestamps": {
"placed_at": "2023-10-01T22:20:26Z",
"created_at": "2023-10-01T22:21:44Z",
"pickup_at": "2023-10-01T23:45:00Z",
"accepted_at": "2023-10-01T23:14:44Z",
"cancelled_at": null,
"cancelled_by": null,
"completed_at": "2023-10-01T23:30:45Z",
"delivery_at": "2023-10-01T23:45:00Z",
"scheduled_for": "2023-10-01T23:45:00Z"
}
}