Webhooks
Outbound events. When something happens in Next Restaurant, an HTTP POST is sent to the endpoints you configure — so another system can react without polling.
Configure them under Integrations.
Setting up
- Enable the API module under Apps.
- Go to Integrations.
- Add one or more endpoint URLs.
- Select which events to send.
- Set a signing secret.
- Use Test webhook to fire a
pingat every endpoint and confirm they receive it.

The events
| Event | Fires when |
|---|---|
order.placed |
A new order is created, on any channel |
order.updated |
An existing order changes |
order.status_changed |
An order moves to a new status |
order.cancelled |
An order is cancelled, with the reason |
ping |
You press Test webhook — for connectivity checks only |
Select only the events you actually consume. Every selected event is delivered to every configured endpoint. Selecting none means all of them, not none.
The payload
Each delivery is a POST with a JSON body of this shape:
{
"event": "order.placed",
"data": { },
"timestamp": "2026-08-28T19:30:00+00:00"
}
| Field | Meaning |
|---|---|
event |
The event name from the table above |
data |
Identifiers for what changed — see below |
timestamp |
ISO 8601, when the event was dispatched |
What data contains
data is a small set of identifiers, not the whole order. It tells you what changed so you
can decide whether to care; it is not a substitute for your own record of the order.
| Event | data fields |
|---|---|
order.placed (storefront, QR or API) |
id, location_id, channel, source |
order.placed / order.updated (created or edited in the admin or POS) |
id, location_id, channel, status |
order.status_changed |
id, status |
order.cancelled |
id, status, reason, location_id |
ping |
message, at |
{
"event": "order.placed",
"data": { "id": 580, "location_id": 2, "channel": "web", "source": "api" },
"timestamp": "2026-09-07T20:42:51+00:00"
}
id is the internal order id, and it is the key to join on. Note that order.placed carries
source when the order came from the storefront, QR ordering or the REST API, and status when it
was created in the back office — so read both defensively.
There is no endpoint to fetch an order by id. The REST API covers locations, menu and order creation only. If you need the full contents of an order later, store the response you got when you created it — webhooks tell you an order changed, not what is in it.
Write your consumer to ignore fields it doesn't recognise. New fields are added to payloads over time, and a consumer that rejects unknown keys will break on an upgrade it didn't need to care about.
Verifying deliveries are genuine
Set a signing secret in Integrations and verify it on your side before acting on a payload.
An endpoint URL is not a secret — it can be guessed, logged by a proxy, or leak from a config file. Without signature verification, anyone who learns the URL can post fabricated orders to your integration. With it, a forged request fails the check and is discarded.
Verify the signature before parsing or acting on the body, not after.
The header is X-NextRestaurant-Signature, and its value is sha256= followed by the HMAC-SHA256
of the raw request body, keyed with your signing secret. Compute it over the bytes as received —
re-serialising the JSON first will change them and the check will fail. Compare with a
constant-time function.
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, $YOUR_SECRET);
if (!hash_equals($expected, $_SERVER['HTTP_X_NEXTRESTAURANT_SIGNATURE'] ?? '')) {
http_response_code(401);
exit;
}
With no secret set, deliveries go out unsigned — the header is simply absent, and a consumer that verifies will reject every one of them. Set the secret before you write the verification, and check the delivery log below to confirm what is actually being sent.
Delivery log
Integrations shows recent delivery attempts, so you can see whether an endpoint received an
event and how it responded, and whether it was signed. It is the first place to look when an
integration "didn't get" something — usually the delivery went out and the receiving end rejected
it. A run of signed: no rows means the signing secret is empty.
Choosing endpoints
- The endpoint must be reachable from your server over HTTP(S). A URL on a private network your Joomla host can't reach will never receive anything.
- Use HTTPS. Payloads carry order data.
- Respond quickly. Acknowledge receipt and do the slow work asynchronously; a consumer that takes a long time to reply makes delivery unreliable.
- Be idempotent. Design the consumer so that receiving the same event twice does not produce two outcomes. This is standard practice for any webhook consumer and it costs very little to build in.
Webhooks versus the API
They solve different problems and are usually used together:
| Use | Reach for |
|---|---|
| "Tell me when an order is placed" | Webhooks |
| "Give me the current menu" | REST API |
| "Place an order from my kiosk" | REST API |
| "Keep my dashboard in sync" | Webhooks to invalidate, API to fetch |
Polling the API on a timer to detect new orders is the wrong shape — it is slower, heavier, and misses changes between polls.
Payment webhooks are different
The webhooks described here are outbound, from Next Restaurant to you.
Payment providers also send inbound webhooks — from PayPal to your site — to confirm settlements. Those are configured per gateway, not here. See Payment Gateways.
See also
- REST API — the inbound API
- Payment Gateways — inbound provider webhooks
- Apps & Modules — enabling the API module