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.
🔠 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"
}
}