Automated Order Placement
Customer places an order through WhatsApp. Wave authenticates the customer, selects the outlet, adds items into cart, and returns the cart for checkout.
Postman collection for WhatsApp / Wave webhook integration APIs.
Generated from Postman collection. Selected API version.
This document describes the Cuscapi WhatsApp / Wave webhook integration APIs generated from the Postman collection.
X-WAVE-TIMESTAMP and X-WAVE-SIGNATURE. Public APIs only require standard JSON headers.| Environment | Base URL |
|---|---|
| Local | http://cuscapi-merchant.localhost |
| Staging | https://cuscapi-merchant-api.coder.com.my |
| Production | TBC |
All Wave-authenticated webhook requests must include these headers:
| Header | Value |
|---|---|
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
X-WAVE-SIGNATURE | {{wave_signature}} |
payload = "{timestamp}.{raw_request_body}"
signature = HMAC_SHA256(payload, webhook_secret)
The raw request body must match exactly. Do not reformat the JSON body after generating the signature.
const secret = pm.collectionVariables.get("wave_secret");
const timestamp = Math.floor(Date.now() / 1000).toString();
pm.collectionVariables.set("wave_timestamp", timestamp);
let body = "";
if (pm.request.body && pm.request.body.mode === "raw") {
body = pm.variables.replaceIn(pm.request.body.raw || "");
}
const payload = timestamp + "." + body;
const signature = CryptoJS.HmacSHA256(payload, secret)
.toString(CryptoJS.enc.Hex);
pm.collectionVariables.set("wave_signature", signature);<?php
$secret = 'your-webhook-secret';
$body = [
'phone' => '60168880366',
'whatsapp_name' => 'Boon',
];
$json = json_encode($body, JSON_UNESCAPED_SLASHES);
$timestamp = time();
$payload = $timestamp . '.' . $json;
$signature = hash_hmac('sha256', $payload, $secret);
$headers = [
'X-WAVE-TIMESTAMP' => $timestamp,
'X-WAVE-SIGNATURE' => $signature,
];$timestamp = $request->header('X-WAVE-TIMESTAMP');
$signature = $request->header('X-WAVE-SIGNATURE');
$payload = $timestamp . '.' . $request->getContent();
$expected = hash_hmac(
'sha256',
$payload,
config('services.wave.webhook_secret')
);
if (! hash_equals($expected, $signature)) {
abort(401, 'Invalid signature.');
}| Variable | Default Value |
|---|---|
base_url | https://your-domain.com |
wave_secret | your-wave-signature |
wave_timestamp | {{$timestamp}} |
sanctum_token | your-sanctum-token |
phone | 60168880366 |
whatsapp_name | Boon |
outlet_id | 1 |
product_id | 4 |
product_with_option_id | 6 |
product_option_group_id | 7 |
product_option_item_id | 2 |
wave_signature | |
base_url_local | |
base_url_staging | |
Integration Scenarios
These scenarios describe common WhatsApp / Wave integration flows and how the API endpoints are used together.
Customer places an order through WhatsApp. Wave authenticates the customer, selects the outlet, adds items into cart, and returns the cart for checkout.
Wave authenticates the customer and retrieves the customer order list. When the order status changes, Cuscapi server triggers a WhatsApp message notification to update the customer in real time.
POST /webhook-api/v1/customers/token using the customer phone number.GET /webhook-api/v1/customers/orders to retrieve the customer's latest orders.Customer checks loyalty points and available vouchers through WhatsApp.
Customer starts an order from desktop web, selects pickup outlet, adds items to cart, then continues checkout.
Customer starts cart activity on web and continues the same cart flow in WhatsApp.
| Header | Value |
|---|---|
Accept | application/json |
No request body.
{
"success": true,
"message": "Outlet list retrieved successfully.",
"data": [
{
"id": 3,
"name": "City Mall Branch",
"phone": "082888999",
"address_line_1": "Level 2, City Mall",
"address_line_2": "Unit C-12",
"city": "Kuching",
"postal_code": "93300",
"state_id": 1982,
"country_id": 129,
"latitude": "1.15500000",
"longitude": "110.15000000",
"status": 1,
"today_opening_hour": {
"day_of_week": 5,
"open_time": "10:00:00",
"close_time": "23:00:00",
"last_order_time": "22:30:00",
"status": 1,
"accept_order_status": 1,
"accept_order_start_time": "10:00:00",
"accept_order_end_time": "22:30:00",
"last_order_time_override": null,
"is_open_now": true,
"is_accepting_order_now": true
},
"opening_hours": [
{
"day_of_week": 1,
"open_time": "10:00:00",
"close_time": "22:30:00",
"last_order_time": "22:00:00",
"status": 1,
"accept_order_status": 1,
"accept_order_start_time": "10:00:00",
"accept_order_end_time": "22:00:00",
"last_order_time_override": null
},
{
"day_of_week": 5,
"open_time": "10:00:00",
"close_time": "23:00:00",
"last_order_time": "22:30:00",
"status": 1,
"accept_order_status": 1,
"accept_order_start_time": "10:00:00",
"accept_order_end_time": "22:30:00",
"last_order_time_override": null
},
{
"day_of_week": 7,
"open_time": "10:00:00",
"close_time": "22:00:00",
"last_order_time": "21:30:00",
"status": 1,
"accept_order_status": 1,
"accept_order_start_time": "10:00:00",
"accept_order_end_time": "21:30:00",
"last_order_time_override": null
}
],
"distance_km": 0,
"distance_label": "0.00km away"
},
{
"id": 2,
"name": "SUSHI KING Damansara",
"phone": "082123456",
"address_line_1": "No. 1, Jalan Equine 9, Taman Equine, 43300 Seri Kembangan, Selangor",
"address_line_2": null,
"city": "Kuching",
"postal_code": "93350",
"state_id": 1983,
"country_id": 129,
"latitude": "1.55330000",
"longitude": "110.35920000",
"status": 1,
"today_opening_hour": {
"day_of_week": 5,
"open_time": "11:00:00",
"close_time": "21:30:00",
"last_order_time": "21:30:00",
"status": 1,
"accept_order_status": 1,
"accept_order_start_time": "11:00:00",
"accept_order_end_time": "21:30:00",
"last_order_time_override": null,
"is_open_now": true,
"is_accepting_order_now": true
},
"opening_hours": [
{
"day_of_week": 0,
"open_time": "11:00:00",
"close_time": "21:30:00",
"last_order_time": "21:30:00",
"status": 1,
"accept_order_status": 1,
"accept_order_start_time": "11:00:00",
"accept_order_end_time": "21:30:00",
"last_order_time_override": null
},
{
"day_of_week": 7,
"open_time": "10:00:00",
"close_time": "22:00:00",
"last_order_time": "21:30:00",
"status": 1,
"accept_order_status": 1,
"accept_order_start_time": "10:00:00",
"accept_order_end_time": "21:30:00",
"last_order_time_override": null
}
],
"distance_km": 50.02,
"distance_label": "50.02km away"
},
{
"id": 6,
"name": "SUSHI KING Taman Equine",
"phone": null,
"address_line_1": "No. 1, Jalan Equine 9, Taman Equine, 43300 Seri Kembangan, Selangor",
"address_line_2": null,
"city": "",
"postal_code": "",
"state_id": 1983,
"country_id": 129,
"latitude": "0.00000000",
"longitude": "0.00000000",
"status": 1,
"today_opening_hour": {
"day_of_week": 5,
"open_time": "11:00:00",
"close_time": "21:30:00",
"last_order_time": "21:30:00",
"status": 1,
"accept_order_status": 1,
"accept_order_start_time": "11:00:00",
"accept_order_end_time": "21:30:00",
"last_order_time_override": null,
"is_open_now": true,
"is_accepting_order_now": true
},
"opening_hours": [
{
"day_of_week": 0,
"open_time": "11:00:00",
"close_time": "21:30:00",
"last_order_time": "21:30:00",
"status": 1,
"accept_order_status": 1,
"accept_order_start_time": "11:00:00",
"accept_order_end_time": "21:30:00",
"last_order_time_override": null
}
],
"distance_km": 12247.65,
"distance_label": "12,247.65km away"
}
]
}No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
No request body.
{
"success": true,
"message": "Product list retrieved successfully.",
"data": [
{
"id": 1,
"sku": "5806",
"slug": "ten-don-s-2-5806",
"name": "Ten Don (S)",
"description": "Japanese curry rice bowl with juicy chicken hambagu.",
"category": "Rice",
"price": 0
},
{
"id": 2,
"sku": "2519",
"slug": "yakiniku-curry-2-2519",
"name": "Yakiniku Curry",
"description": "Crispy chicken katsu served with rich Japanese curry and rice.",
"category": "Rice",
"price": 22.9
},
{
"id": 3,
"sku": "OUT2-ABURISALMO",
"slug": "aburi-salmon-rice-bowl",
"name": "Aburi Salmon Rice Bowl",
"description": "Seared salmon served over warm Japanese rice.",
"category": "Rice",
"price": 24.9
},
{
"id": 4,
"sku": "2897",
"slug": "vida-zero-salty-lychee-2-2897",
"name": "Vida Zero Salty Lychee",
"description": "Rich broth ramen with chicken slices and egg.",
"category": "Noodles",
"price": 5.5
},
{
"id": 5,
"sku": "2899",
"slug": "vida-zero-sakura-2-2899",
"name": "Vida Zero Sakura",
"description": "Spicy miso broth with noodles, egg, and toppings.",
"category": "Noodles",
"price": 5.5
},
{
"id": 6,
"sku": "2227",
"slug": "chicken-nanban-2-2227",
"name": "Chicken Nanban",
"description": "Fresh salmon roll topped with creamy mentai sauce.",
"category": "Combo",
"price": 8.9
},
{
"id": 7,
"sku": "OUT2-SALMONCREA",
"slug": "salmon-cream-cheese-mentaiyaki-with-spicy-karaage-set",
"name": "Salmon Cream Cheese Mentaiyaki with Spicy Karaage Set",
"description": "Aburi-style salmon topped with creamy cheese and mentai mayo, served with spicy karaage and rice.",
"category": "Combo",
"price": 28.9
},
{
"id": 8,
"sku": "OUT2-CHICKENKAT",
"slug": "chicken-katsu-set",
"name": "Chicken Katsu Set",
"description": "Crispy chicken katsu with rice, salad, and soup.",
"category": "Combo",
"price": 26.9
},
{
"id": 9,
"sku": "OUT2-GYOZA",
"slug": "gyoza",
"name": "Gyoza",
"description": "Pan-fried Japanese dumplings.",
"category": "Sides",
"price": 9.9
},
{
"id": 10,
"sku": "OUT2-KARAAGE",
"slug": "karaage",
"name": "Karaage",
"description": "Japanese fried chicken bites.",
"category": "Sides",
"price": 12.9
},
{
"id": 11,
"sku": "OUT2-EDAMAME",
"slug": "edamame",
"name": "Edamame",
"description": "Lightly salted boiled soybeans.",
"category": "Sides",
"price": 6.9
},
{
"id": 12,
"sku": "OUT2-COLAORIGIN",
"slug": "cola-original-can",
"name": "Cola Original (Can)",
"description": "Chilled canned soft drink.",
"category": "Drinks",
"price": 3
},
{
"id": 13,
"sku": "OUT2-ICEDGREENT",
"slug": "iced-green-tea",
"name": "Iced Green Tea",
"description": "Refreshing iced Japanese green tea.",
"category": "Drinks",
"price": 4.9
},
{
"id": 27,
"sku": "2310",
"slug": "tenpura-udon-2-2310",
"name": "Tenpura Udon",
"description": "Japanese wheat noodles in soybean-based soup served with tenpura prawns & vegetables.",
"category": "NOODLES",
"price": 19.5
},
{
"id": 28,
"sku": "2305",
"slug": "nabeyaki-udon-2-2305",
"name": "Nabeyaki Udon",
"description": "Japanese wheat noodles, beancurd skin, chicken, tenpura prawn, crabstick & lotus root served in soybean-based soup.",
"category": "NOODLES",
"price": 19.5
}
]
}Sample shown with selected menu items. Actual response may contain more products depending on the outlet menu.
No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
Content-Type | application/json |
{
"keyword": "chicken",
"outlet_id": {{outlet_id}}
}No saved example response in the Postman collection.
No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
No request body.
No saved example response in the Postman collection.
No request-level script.
| Header | Value |
|---|---|
Content-Type | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
{
"phone": "{{phone}}",
"whatsapp_name": "{{whatsapp_name}}"
}{
"success": true,
"customer": {
"id": 4,
"name": "Boon",
"phone_number": null
},
"access_token": "49|8ScI0tDMy8JLZ4SkTYOSOYOqurjG9FFNR0uWhZFlf11c3084",
"token_type": "Bearer",
"login_token": "WAfucJARYrnbfRrEfM6OE66QZHPZxszqjoYbMFI4sYL4y7Y3qoG7x0iQHIEgvF5dVGWEAjjDMLwlcA5h",
"login_url": "http://localhost:3000/wave-login?token=WAfucJARYrnbfRrEfM6OE66QZHPZxszqjoYbMFI4sYL4y7Y3qoG7x0iQHIEgvF5dVGWEAjjDMLwlcA5h",
"expires_in_minutes": 10
}No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
Content-Type | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
{
"phone": "{{phone}}",
"whatsapp_name": "{{whatsapp_name}}"
}{
"success": true,
"message": "Outlet selected successfully.",
"data": {
"customer": {
"id": 4,
"name": "Boon",
"phone": "60168880366"
},
"cart": {
"id": 61,
"outlet_id": 2,
"delivery_option": "pickup"
},
"outlet": {
"id": 2,
"name": "SUSHI KING Damansara"
}
}
}No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
Content-Type | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
{
"phone": "{{phone}}",
"whatsapp_name": "{{whatsapp_name}}"
}{
"success": true,
"message": "Outlet selected successfully.",
"data": {
"customer": {
"id": 4,
"name": "Boon",
"phone": "60168880366"
},
"cart": {
"id": 61,
"outlet_id": 2,
"delivery_option": "dine_in"
},
"outlet": {
"id": 2,
"name": "SUSHI KING Damansara"
}
}
}No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
Content-Type | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
{
"phone": "{{phone}}",
"whatsapp_name": "{{whatsapp_name}}"
}No saved example response in the Postman collection.
No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
Content-Type | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
{
"phone": "{{phone}}",
"whatsapp_name": "{{whatsapp_name}}",
"product_id": {{product_id}},
"quantity": 2,
"note": "Less spicy"
}No saved example response in the Postman collection.
const secret = pm.collectionVariables.get("wave_secret");
const timestamp = Math.floor(Date.now() / 1000).toString();
pm.collectionVariables.set("wave_timestamp", timestamp);
let body = "";
if (pm.request.body && pm.request.body.mode === "raw") {
body = pm.variables.replaceIn(pm.request.body.raw || "");
}
const payload = timestamp + "." + body;
const signature = CryptoJS.HmacSHA256(payload, secret)
.toString(CryptoJS.enc.Hex);
pm.collectionVariables.set("wave_signature", signature);
console.log("payload:", payload);
console.log("signature:", signature);| Header | Value |
|---|---|
Accept | application/json |
Content-Type | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
{
"phone": "{{phone}}",
"whatsapp_name": "{{whatsapp_name}}",
"product_id": {{product_with_option_id}},
"quantity": 1,
"note": "No onion",
"options": [
{
"product_option_group_id": {{product_option_group_id}},
"product_option_item_ids": [
{{product_option_item_id}}
]
}
]
}No saved example response in the Postman collection.
No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
No request body.
No saved example response in the Postman collection.
No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
{
"phone": "{{phone}}"
}No saved example response in the Postman collection.
No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
No request body.
{
"success": true,
"customer": {
"id": 4,
"name": "Boon",
"phone": "60168880366"
},
"points_balance": 850,
"points_summary": {
"earned": 0,
"redeemed": 0,
"expired": 0,
"adjusted": 0
},
"customer_vouchers": [
{
"id": 1,
"voucher_id": 2,
"name": "RM15 Voucher",
"status": "available",
"points_used": 150,
"active": "2026-06-25 23:43:52",
"expiry": null
}
]
}{
"success": false,
"message": "Endpoint not found."
}No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
No request body.
No saved example response in the Postman collection.
No request-level script.
| Header | Value |
|---|---|
Accept | application/json |
Content-Type | application/json |
X-WAVE-SIGNATURE | {{wave_signature}} |
X-WAVE-TIMESTAMP | {{wave_timestamp}} |
{
"phone": "{{phone}}",
"whatsapp_name": "{{whatsapp_name}}",
"voucher_id": 2
}No saved example response in the Postman collection.
No request-level script.