API and webhooks reference

Last updated: 19 October 2026

1. Overview

An account's owners connect QR Wait to their own systems on the admin page's Integrations screen: webhooks push queue events out, API keys let a POS or CRM read the queues and act on them. Managers and attendants can't see or change either.

Connecting without code, through Zapier or Make: the webhook recipe.

2. Webhook endpoints

Up to 5 endpoints per account. An endpoint is an https:// address on a public host name: IP literals, localhost, names without a dot, .local / .internal / .lan / .home / .arpa names and QR Wait's own domain are refused.

3. Events

TypeWhen
ticket.joinedA ticket is taken: QR join, kiosk, added by an attendant or the API, or an appointment checked in
ticket.calledA ticket is called (by hand, "Call next", or automatically when places free up)
ticket.servedA called ticket is marked "Arrived"
ticket.no_showA called ticket is marked "No-show", or its time to arrive runs out
ticket.cancelledThe visitor leaves the queue, or an attendant removes a waiting ticket
appointment.bookedAn appointment is booked (by the visitor or the attendant)
ping"Send test event" on the Integrations screen

Undoing an action in the attendant panel doesn't send anything; a ticket moved in from another queue of the account sends no ticket.joined there.

4. Payload

POST with content-type: application/json:

  • queue.slug is left out for a hidden queue (its address is a secret).
  • ticket.source: qr, manual (attendant or API), kiosk or appointment. service, counter, table and zone appear when the queue uses them.
  • appointment.booked carries appointment: { at, size, service? } instead of ticket.
  • Personal data only when the owner ticked "Include names, join form answers and booking codes" for the endpoint: visitor: { name?, answers? } (answers keyed by the question) and appointment.code. Off by default: payloads are numbers and times only.
{
  "id": "d_3f9a1c0b7e2d4a61",
  "type": "ticket.called",
  "created": "2026-10-10T09:41:07.512Z",
  "account": "antalyabb",
  "queue": { "id": "a1b2c3d4e5", "name": "Konyaaltı Beach", "slug": "konyaalti" },
  "ticket": {
    "no": 42, "size": 2, "source": "qr",
    "joined_at": "2026-10-10T09:12:30.004Z", "called_at": "2026-10-10T09:41:07.511Z",
    "service": "Payments", "counter": "3"
  }
}

5. Headers and signature

HeaderMeaning
X-QRWait-EventThe event type
X-QRWait-DeliveryThe delivery id (also id in the body); the same on every retry, so a receiver can drop duplicates
X-QRWait-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256> of the string <t>.<raw body>, keyed with the endpoint's secret
import { createHmac, timingSafeEqual } from "node:crypto";
const [, t, v1] = req.headers["x-qrwait-signature"].match(/^t=(\d+),v1=([a-f0-9]{64})$/);
const want = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const ok = timingSafeEqual(want, Buffer.from(v1, "hex")) && Math.abs(Date.now() / 1000 - Number(t)) < 300;

The secret (whsec_…) is shown once when the endpoint is created. To verify: compute the HMAC over t + "." + body with the secret, compare it with v1 in constant time, and reject a t more than a few minutes old.

6. Delivery and retries

Events are usually delivered within a second. A queue checks whether any endpoint is on at most once a minute, so a new endpoint can miss up to a minute of events.

  • Success is any 2xx answer within 10 seconds; redirects are not followed.
  • Otherwise the delivery is retried after 1 min, 5 min, 30 min, 2 h and 6 h (6 tries in about 9 hours), then dropped.
  • After 15 failed attempts in a row an endpoint is turned off, its waiting deliveries are dropped and the account's email gets a notice. Turning it back on clears the count. Test events don't count.
  • At most 200 deliveries wait per account; beyond that the oldest are dropped.
  • The Integrations screen shows the last 50 attempts: time, event, endpoint and the answer's status (0: no answer). Bodies aren't kept.

Endpoints and their secrets are stored with the account and are in the daily backup; the delivery queue and log are not.

7. REST API: keys and limits

Base: https://qrwait.app/api/v1. Every request carries an API key:

Authorization: Bearer qrw_<12 hex>_<48 hex>

Keys are made on the Integrations screen (up to 10 per account), shown once, stored as a SHA-256 hash, and revoked there. A read key may only GET; a write key may also POST. 120 requests a minute per key, and 120 a minute from one IP address whatever the key. POST bodies are JSON (content-type: application/json). Write requests may carry an Idempotency-Key header (up to 64 characters): a retried request with the same key does nothing the second time. Actions show in the queue's action log as API · <key name>.

8. REST API: endpoints

Method and pathScopeBodyAnswer
GET /queuesread{ queues: [status, …] } for every queue of the account
GET /queues/<id>readstatus (below)
POST /queues/<id>/ticketswrite{ size?: 1, note?: "…", service?: "…" }{ no }: a walk-in, as the attendant's "Add"
POST /queues/<id>/callwrite{ counter?: "3" }{ no } of the group called, null if nobody waits. In a counters queue counter is required unless it has one counter; elsewhere the first waiting group is called
POST /queues/<id>/servedwrite{ no: 42 }{ ok: true }: "Arrived" for that called number

Status: { id, name, waiting, people, next, called: [{ no, at, desk?, table?, zone?, svc? }], eta, paused, open, full, services: [{ name, waiting, eta }] }. eta is the estimated wait in minutes for someone joining now (null without enough recent calls). Queue ids (10 characters) come from GET /queues.

9. Errors

Errors are JSON { error } in the language of the x-lang header (English by default): 401 a missing, wrong or revoked key, 403 a read key on a write route, 404 an unknown route or a queue of another account, 429 over the rate limit, 400 anything else the queue refuses (no tickets left, an unknown counter, a number that isn't called).