Skip to content

Webhooks

Quiqqy pushes events to your HTTPS endpoint — orders never require polling. Every product uses the same transport: the same headers, the same signature scheme, the same retry ladder. Per-event payloads are documented in Online Ordering events and Hub events.

Deliveries are POST requests with a JSON body and these headers:

HeaderValue
Content-Typeapplication/json
X-Quiqqy-SignatureHMAC-SHA256 of the raw request body, keyed with your webhook_secret, as lowercase hex
X-Quiqqy-EventThe event type, e.g. order.created
X-Quiqqy-Delivery-IdUnique delivery id (UUID) — your idempotency key
X-Quiqqy-Location-IdYour external_store_id for the store, when set
X-Quiqqy-ActionOnly on action_required — equals data.action (data.request_type is a legacy alias)

Every event shares one envelope shape — event_type, event_id, timestamp and a data object. The tenant fields vary by product: Online Ordering events carry store_id and location_id, Hub events carry community_id. An Online Ordering example:

{
"event_type": "order.created",
"event_id": "9b3e6a1c-4f2d-4e8a-b1c9-7d5e2f8a0c4b",
"store_id": "st_8fk2c1p9",
"location_id": "LOC-001",
"data": { "": "event-specific payload" },
"timestamp": "2026-07-30T12:34:56.000Z"
}

event_id always equals X-Quiqqy-Delivery-Id for the original delivery and stays the same across retries.

Compute HMAC-SHA256(webhook_secret, raw_body) and compare it with X-Quiqqy-Signature using a constant-time comparison. Use the exact raw body bytes as received — re-serializing the parsed JSON changes whitespace and key order and the signature will not match. Reject requests that fail verification; the signature, not the payload shape, is what makes a delivery trustworthy.

Your webhook_secret is returned once by POST /pos/integrate-store (or shown once in the console for other products). Store it server-side.

import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post(
'/webhooks/quiqqy',
express.raw({ type: 'application/json' }), // keep the raw bytes
(req, res) => {
const signature = req.get('X-Quiqqy-Signature') ?? '';
const expected = crypto
.createHmac('sha256', process.env.QUIQQY_WEBHOOK_SECRET)
.update(req.body) // Buffer of the raw body — never JSON.stringify(parsed)
.digest('hex');
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) return res.status(401).end();
res.status(200).end(); // acknowledge first …
const event = JSON.parse(req.body); // … then process
enqueue(event); // heavy work belongs off the request path
},
);

Quiqqy waits 10 seconds for a response. A 2xx acknowledges the delivery. Anything else — a non-2xx status, a timeout, a connection error, or a 3xx redirect (redirects are treated as failure; register a direct HTTPS URL) — schedules a retry:

AttemptDelay after the previous failure
1immediate
21 minute
35 minutes
430 minutes
52 hours
66 hours

Six attempts in total. After the sixth failure the delivery is marked failed and is visible in the console’s delivery logs, where you can inspect the payload, your response status and the first part of your response body.

Retries mean your endpoint will see the same event more than once. Dedupe on X-Quiqqy-Delivery-Id (equal to event_id): record the id when you accept a delivery and return 200 immediately for any id you have already seen. Ordering is not guaranteed across events — use timestamp and the resource’s own state, not arrival order.

EventFires whenProduct
order.createdA new order is placed for an integrated storeOnline Ordering
order.updatedAn order’s status advancedOnline Ordering
order.cancelledAn order was cancelled by either sideOnline Ordering
order.deliveredAn order reached its terminal delivered stateOnline Ordering
menu.updatedThe store’s menu changed on the Quiqqy sideOnline Ordering
menu.item_availability_changedAn item was 86’d or restored on the Quiqqy sideOnline Ordering
store.status_changedThe store went live or was pausedOnline Ordering
action_requiredQuiqqy asks your integration to do something (e.g. push the menu)Online Ordering
order.createdA community order is placedHub Ordering
order.status_changedA community order’s status advancedHub Ordering

Build your handler to switch on event_type and ignore unknown events and unknown fields. New event types and payload fields are added without notice; that is not a breaking change.

  1. Verify X-Quiqqy-Signature against the raw body — reject on mismatch.
  2. Dedupe on X-Quiqqy-Delivery-Id — deliveries repeat.
  3. Return 2xx within 10 seconds; do heavy work asynchronously.
  4. Dispatch on event_type; for action_required, on data.action.
  5. Ignore unknown event types and unknown fields.
  6. Use a direct HTTPS URL — no redirects, valid TLS certificate.