Webhooks
Have OrderRestro tell your other tools the moment something happens — a paid bill to your accounting software, a new delivery to WhatsApp, a booking to a spreadsheet.
Set one up
Open Settings > Webhooks, enter the URL that should receive events, tick the events you want, and choose Add webhook. The signing secret (it starts with whsec_) is shown once — copy it. Use Send test to check your receiver, and Log to see recent deliveries and why any failed.
Webhooks are the integration point for tools that don’t have a native connector: point one at an automation service (n8n, Zapier, Make) or your own server. For reading data or placing orders, see the REST API.
What you receive
Each event is an HTTP POST with a JSON body and these headers:
X-OrderRestro-Event— the event nameX-OrderRestro-Delivery— a unique id for this delivery; use it to ignore duplicatesX-OrderRestro-Signature—t=<unix seconds>,v1=<hex>
{
"event": "order.paid",
"createdAt": "2026-10-05T11:42:10.512Z",
"data": {
"branchId": "cmui15lbm002ncv01wmaofjx1",
"orderId": "cmuu38vk900181k2xtb4e35mk",
"orderNumber": "20261005-0014",
"type": "TAKEAWAY",
"total": 480,
"tip": 0,
"discount": 0,
"couponCode": null,
"payments": [{ "method": "UPI", "amount": 480 }]
}
}Answer with any 2xx status to accept. The response body is ignored.
Events
| Event | When | data fields |
|---|---|---|
order.created | A new order was placed (POS, QR, website or API). | orderId, orderNumber, type, channel, total, deliveryAddress, deliveryPhone, scheduledFor |
order.paid | A bill was paid. | orderId, orderNumber, type, total, tip, discount, couponCode, payments[{method, amount}] |
order.cancelled | An order was cancelled or an online order was rejected. | orderId, orderNumber, type |
reservation.created | A table was booked. | reservationId, customerName, phone, guestCount, reservedAt, channel |
delivery.updated | A driver was assigned or the delivery status changed. | orderId, orderNumber, status, driver, address |
campaign.message | A marketing message to deliver by SMS or WhatsApp (one event per recipient). | campaignId, channel, to, name, message |
Every data object also carries the branchId it happened at. A test event is sent by the Send test button.
Verify the signature
Anyone can POST to your URL, so always check the signature. v1 is the HMAC-SHA256, in hex, of the string {t}.{raw request body} using your signing secret. Use the raw body (before any JSON parsing), compare in constant time, and reject timestamps more than five minutes old.
import crypto from "node:crypto";
export function verify(secret, rawBody, header) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const mac = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const a = Buffer.from(mac), b = Buffer.from(parts.v1 ?? "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hmac, hashlib, time
def verify(secret: str, raw_body: bytes, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
signed = parts["t"].encode() + b"." + raw_body
mac = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, parts.get("v1", ""))Retries & the delivery log
If your endpoint is down or answers anything other than 2xx, OrderRestro tries again after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, then marks the delivery failed. Each attempt has a timeout of 8 seconds and redirects are not followed. Because of retries, an event can arrive more than once — use X-OrderRestro-Delivery to de-duplicate. Delivery history is kept for 30 days.
Network rules
For safety, webhook URLs must use https and may not point at a private, loopback or link-local address (so the server can’t be tricked into calling your internal network). The address is checked again on every attempt.
If OrderRestro runs on your restaurant’s own network and the receiver is a device on it, the operator can allow it by setting WEBHOOKS_ALLOW_PRIVATE=true (and WEBHOOKS_ALLOW_INSECURE=true for plain http) on the server.