Documentation for configuring, enabling, and verifying the signature of incoming webhook requests.
1) How to get the settings
Merchant → Merchant name → Settings
- Webhook URL — the URL of your handler.
- Token — used as
webhook_secret(HMAC signing secret).
Token is a secret. Do not publish it or store it in plain text. Use it as an HMAC key.
2) How to enable webhook delivery
Statuses & path → Order status → [status] → Webhook on / off
A webhook is sent when the order transitions to a status where Webhook = ON.
3) Request format
- Method:
POST - Content-Type:
application/json - Body: JSON order object
- X-Signature: request signature (HMAC)
The signature is calculated from the raw request body (raw JSON). To verify it, use the original request body
(e.g. php://input) rather than a re-encoded json_encode().
4) Request signature (HMAC SHA-256)
4.1 How the platform generates the signature
The JSON string sent in the webhook body is signed.
$payload = json_encode($data, JSON_UNESCAPED_UNICODE);
$sign = hash_hmac('sha256', $payload, $merchant['token']);
The signature is passed in the header:
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Signature: ' . $sign
],
4.2 Signature verification
Webhook signature verification is performed to ensure:
- the request was actually sent by our platform
- the request body was not modified in transit
- the data was not forged by third parties
Below is the general algorithm (language-agnostic).
General principle
We use the HMAC-SHA256 algorithm.
The signature is generated as follows:
signature = HMAC_SHA256(raw_request_body, webhook_secret)
Where:
raw_request_body— the exact HTTP request body in its original (raw) formwebhook_secret— the Token from Merchant → Settings
The resulting signature is sent in the X-Signature header.
PHP example
<?php
// Token from Merchant → Settings (used as webhook_secret)
$secret = 'WEBHOOK_TOKEN_FROM_MERCHANT_SETTINGS';
// raw body as received (important!)
$rawBody = file_get_contents('php://input');
// signature from X-Signature header
$received = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
// calculate signature
$calculated = hash_hmac('sha256', $rawBody, $secret);
// compare in constant time
if (!hash_equals($calculated, $received)) {
http_response_code(401);
echo 'Invalid signature';
exit;
}
http_response_code(200);
echo 'OK';
Always verify against the raw body. If you parse JSON and run json_encode() again,
key order/escaping may differ and the signature will not match.
Python example
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "WEBHOOK_TOKEN_FROM_MERCHANT_SETTINGS"
@app.route("/webhook", methods=["POST"])
def webhook():
# Raw body exactly as received
raw_body = request.get_data() # bytes
# Signature from header
received_signature = request.headers.get("X-Signature", "")
# Calculate HMAC-SHA256
calculated_signature = hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
raw_body,
hashlib.sha256
).hexdigest()
# Constant-time comparison
if not hmac.compare_digest(calculated_signature, received_signature):
abort(401, "Invalid signature")
return "OK", 200
if __name__ == "__main__":
app.run(port=5000)
Always use the raw request body (request.get_data()).
Do not re-serialize JSON before calculating the signature, otherwise the hash may not match.
5) Payload fields
| Field | Type | Description |
|---|---|---|
| order_id | number | Order ID |
| user | object | User data |
| user.user_id | string | User ID |
| user.name | string | User name |
| user.phone | string | Phone |
| user.email | string|null | |
| type | number | Order type (internal) |
| company | number | Company ID |
| merchant | number | Merchant ID |
| destination_address | object | Address in multi-language format (EN/IL/ES/RU) |
| destination_coordinates | object | Delivery coordinates |
| destination_coordinates.lat | string | Latitude |
| destination_coordinates.lon | string | Longitude |
| destination_info | string | Additional info (plain string) |
| destination_info_json | array | Additional info (structured) |
| comment | string | Customer comment |
| info | string | Additional information (service field) |
| cost | number | Items subtotal (base) |
| discount | number | Discount |
| bonus | number | Bonuses |
| promocode | string | Promo code |
| promocode_amount | number | Promo discount amount |
| tips | number | Tips (amount) |
| tips_percent | number | Tips (percent) |
| fee | number | Service fee |
| vat | number | VAT |
| delivery | number | Delivery fee |
| payment_commission | number | Payment provider commission |
| total | number | Total to pay |
| paid | bool | Whether the order is paid |
| order_status | object | Current order status |
| order_status.status | string | Status ID |
| order_status.value | object | Status name (multi-language) |
| time_create | string | Created at (YYYY-MM-DD HH:MM:SS) |
| last_time | string | Last update time |
| time_finish | string|null | Completed at |
| time_delivery | string|null | Delivered at |
| courier | null|object | Courier data (if assigned) |
| cart | array | Order items |
| cart[].id | number | Product ID |
| cart[].qty | number | Quantity |
| cart[].price | number | Unit price |
| cart[].discount | number | Item discount |
| cart[].amount | number | Item amount |
| cart[].total | number | Item total |
| cart[].name | object | Name (multi-language) |
| cart[].description | object | Description (multi-language) |
| cart[].addons | bool|array | Add-ons (if used) |
| cart[].balance | number | Stock/balance (service field) |
| currency | string | Currency symbol |
| is_pickup_point | bool | Pickup point flag |
| external_id | string|null | External identifier |
Some fields are service fields and may be null depending on the merchant configuration and order scenario.
6) Payload example
Example JSON sent in the webhook request body:
{
"order_id": 2088,
"user": {
"user_id": "107",
"name": "Test",
"phone": "972000000000",
"email": null
},
"type": 1,
"company": 28,
"merchant": 44,
"destination_address": {
"EN": "HaShita, Ofir, Eilat, Beersheba Subdistrict, South District, 8804625, Israel",
"IL": "HaShita, Ofir, Eilat, Beersheba Subdistrict, South District, 8804625, Israel",
"ES": "HaShita, Ofir, Eilat, Beersheba Subdistrict, South District, 8804625, Israel",
"RU": "HaShita, Ofir, Eilat, Beersheba Subdistrict, South District, 8804625, Israel"
},
"destination_coordinates": {
"lat": "29.556174355301",
"lon": "34.949371218681"
},
"destination_info": "",
"destination_info_json": [],
"comment": "",
"info": "",
"cost": 58,
"discount": 0,
"bonus": 0,
"promocode_amount": 0,
"tips": 3,
"tips_percent": 5,
"fee": 0,
"vat": 0,
"delivery": 0.47,
"payment_commission": 0,
"total": 61.47,
"promocode": "",
"paid": false,
"order_status": {
"status": "4",
"value": {
"EN": "Ready",
"IL": " מוּכָן",
"RU": "Ready"
}
},
"time_create": "2026-02-26 14:25:32",
"last_time": "2026-02-26 17:28:01",
"time_finish": "2026-02-26 15:56:32",
"time_delivery": null,
"courier": null,
"cart": [
{
"addons": false,
"price": 38,
"discount": 0,
"amount": 38,
"total": 38,
"id": 399,
"qty": 1,
"name": {
"EN": "Toast Lebowski",
"IL": "טוסט לבובסקי",
"ES": "Tostada Lebowski",
"RU": "Toast Lebowski"
},
"description": {
"EN": "Tomato sauce, pesto, Gouda, purple onion, tomato and Kalamata olives\nServed with a green salad seasoned with vinaigrette dressing",
"IL": "רוטב עגבניות, פסטו, גבינת גאודה, בצל סגול, עגבנייה וזיתים קלמטה \nמוגש עם סלט ירוק מתובל ברוטב ויניגרט",
"ES": "Salsa de tomate, pesto, Gouda, cebolla morada, tomate y aceitunas Kalamata \nServido con una ensalada verde aderezada con vinagreta",
"RU": "Tomato sauce, pesto, Gouda, purple onion, tomato and Kalamata olives\nServed with a green salad seasoned with vinaigrette dressing"
},
"balance": 1
},
{
"addons": false,
"price": 20,
"discount": 0,
"amount": 20,
"total": 20,
"id": 396,
"qty": 1,
"name": {
"EN": "Summer muesli",
"ES": "Summer muesli",
"RU": "Summer muesli"
},
"description": {
"EN": "Real Greek yogurt with green smith apple, pear, banana, dates, halva and granola with touches of silan and raw tahini",
"ES": "Real Greek yogurt with green smith apple, pear, banana, dates, halva and granola with touches of silan and raw tahini",
"RU": "Real Greek yogurt with green smith apple, pear, banana, dates, halva and granola with touches of silan and raw tahini"
},
"balance": 1
}
],
"max_coock_time": 0,
"delivery_time": 0,
"pay_type": {
"pay_type_id": "2",
"name": {
"EN": "Cash",
"RU": "Cash",
"IL": "כסף מזומן"
}
},
"cutlery": false,
"asap": true,
"pay_link": null,
"pay_date": null,
"pay_info": null,
"platform": null,
"ver": null,
"promocode_type": 1,
"promocode_discount": "0",
"odd": 100,
"print": "0",
"last_order_status": 4,
"currency": "₪",
"is_pickup_point": false,
"external_id": null
}
It is recommended to respond with 200 OK (or any 2xx) after successful processing.
For an invalid signature — 401. For a temporary integrator-side error — 5xx.