Fails open
If the queue service is unreachable or the queue does not exist, the script does nothing and the shop remains fully usable. An outage of the waiting room never blocks the shop.
ADITUS-Queue is a standalone virtual waiting room that protects any website, API or online service against traffic spikes. It integrates seamlessly with the ADITUS ticketing platform — and deploys just as independently in front of any web application. Visitors are held in a hosted waiting room and admitted strictly first-come-first-served at a configured rate per minute. Integration ranges from a single gateway link or script tag with no infrastructure on the operator's side, through enforcement in the reverse proxy or CDN, to the plain REST API.
New: the self-regulating admission rate. The queue reads a health check of your shop and adjusts the rate on its own — more visitors when the shop has headroom, immediate relief when it comes under pressure.
Despite the depth: setup takes minutes, not days. One published link or one script tag — no infrastructure, no deployment, no code changes on your side.
Protect your application in under 2 minutes: copy the script tag into your head or publish the gateway link. No changes to your server infrastructure. Only when you want maximum control do you enable the optional advanced features — everything else on this page is exactly that: optional depth for later.
In the ADITUS admin console: name, shop address, admission rate per minute. Sensible defaults cover everything else.
Either publish the ready-made gateway link instead of the shop link — or paste the prepared script tag into the shop page, exactly like an analytics snippet.
Below the admission rate visitors never see the waiting room. Only when a spike hits does the queue step in — hosted, scaled and operated by us.
The power of the queue is an upgrade path, not a requirement. Level 1 covers the vast majority of use cases on its own.
Which option fits depends on one question: can the published shop link be changed? If yes, the gateway link is the better choice — traffic never reaches the shop before admission. If the published shop URL is fixed, the script tag protects the shop without any change to its publication. Beyond these two, the check can be enforced in the reverse proxy or CDN (below), driven directly via the REST API, or verified in any backend language with a JWT library — Node.js, PHP, Java, .NET, Python, Go.
Each waiting room provides its own entry URL. It is published everywhere the shop link would otherwise appear — newsletter, website, social media. The queue takes the request and decides server-side in a single step: below the admission rate the visitor is forwarded directly to the configured shop address and does not notice the queue at all; once the rate is exceeded, the visitor lands in the waiting room and is forwarded automatically when it is their turn. The shop itself receives no traffic before admission — requests are absorbed before they reach the shop infrastructure.
<!-- Der veröffentlichte Shop-Link zeigt auf die Queue statt auf den Shop: -->
<a href="https://developers.aditus.com/queue/<slug>/enter">Tickets kaufen</a>For this option, the target address of the shop (Ziel-URL) must be configured for the queue — it is the address admitted visitors are forwarded to. Admitted visitors arrive at the shop with a signed admission token in the URL — a standard HS256 JWT that the shop can optionally verify offline in its own backend using the shared secret. Structure, claims and a backend example are documented below. Note: the gateway protects the published entry point; visitors who know the shop URL directly can still bypass it. If that matters, combine the gateway link with the script tag or verify the token in the shop backend.
When the published shop link cannot be changed, the integration is a single script tag. It is delivered together with the shop pages — the same way an analytics snippet is delivered — and must be present on every page that the waiting room is supposed to protect. Placement in the <head> with the defer attribute is recommended; the exact position is not critical.
<!-- Auf jeder zu schützenden Shop-Seite im <head>: -->
<script src="https://developers.aditus.com/queue/<slug>/embed.js" defer></script>The exact tag for a specific waiting room — with the correct slug already inserted — is available in the ADITUS admin console per queue. The script performs three checks on every page load:
Nothing happens. The pass is stored per browser session; the visitor uses the shop without interruption.
The admission token in the URL is verified server-side (signature, queue assignment, expiry). If valid, a session pass is stored and the token is removed from the address bar. Invalid or expired tokens send the visitor back to the waiting room.
If nobody is currently waiting, the visitor is admitted silently in the background and stays on the page — the waiting room is never shown. Only when the configured admission rate is exceeded and a queue has formed is the visitor redirected to the waiting room; the current page is passed along as a validated return URL, so after admission the visitor lands on exactly the page they originally requested.
If the queue service is unreachable or the queue does not exist, the script does nothing and the shop remains fully usable. An outage of the waiting room never blocks the shop.
Return URLs are validated server-side against the domains configured for the queue. The waiting room only forwards to pages of the registered shop.
Both options above protect the published entry point. If the shop must be unreachable without admission even for visitors who know its URL directly, the check moves in front of the shop — into the reverse proxy or CDN that terminates its traffic. The logic is always the same three steps: take the token from the aditus_queue_token URL parameter when the visitor returns from the waiting room and store it as a cookie; on every request, verify the token from the cookie as a standard HS256 JWT with the shared secret (issuer aditus-queue, audience the queue slug, expiry); without a valid token, redirect to the waiting room at https://developers.aditus.com/queue/<slug>. Verification is offline — the proxy never calls the queue and adds no latency. The following sketches show the pattern for four common environments; adapt them to your setup.
# Skizze: Nginx mit njs (js_import) — prüft den Zugriffs-Token als HS256-JWT
# direkt am Proxy, ohne Rückruf an die Queue. An die eigene Umgebung anpassen.
# nginx.conf:
# env ADITUS_QUEUE_SECRET;
# js_import queue from /etc/nginx/queue.js;
# server {
# location / { js_content queue.gate; }
# location @shop { proxy_pass http://shop_backend; }
# }
// /etc/nginx/queue.js:
const crypto = require('crypto');
const SLUG = '<slug>';
const SECRET = process.env.ADITUS_QUEUE_SECRET; // Shared Secret der Queue
function claims(token) {
const p = token.split('.');
if (p.length !== 3) return null;
const sig = crypto.createHmac('sha256', SECRET)
.update(p[0] + '.' + p[1]).digest('base64url');
if (sig !== p[2]) return null;
const c = JSON.parse(Buffer.from(p[1], 'base64url'));
const ok = c.iss === 'aditus-queue' && c.aud === SLUG && c.exp > Date.now() / 1000;
return ok ? c : null;
}
function gate(r) {
const fromUrl = r.args.aditus_queue_token;
const m = (r.headersIn.Cookie || '').match(/(?:^|;\s*)aditus_queue=([^;]+)/);
const token = fromUrl || (m && m[1]);
if (!token || !claims(token)) {
// Kein gültiger Token -> zurück in den Warteraum
r.return(302, 'https://developers.aditus.com/queue/' + SLUG);
return;
}
if (fromUrl) {
// Token aus der Rücksprung-URL in ein Sitzungs-Cookie übernehmen
r.headersOut['Set-Cookie'] =
'aditus_queue=' + fromUrl + '; Path=/; Secure; HttpOnly; Max-Age=600';
}
r.internalRedirect('@shop');
}
export default { gate };# Skizze: Caddy mit dem jwtauth-Plugin (github.com/ggicci/caddy-jwt).
# Token kommt beim Rücksprung als ?aditus_queue_token=… oder danach als Cookie;
# jwtauth prüft beide Quellen. An die eigene Umgebung anpassen.
shop.example.com {
route {
jwtauth {
sign_key {env.ADITUS_QUEUE_SECRET}
sign_alg HS256
from_query aditus_queue_token
from_cookies aditus_queue
issuer_whitelist aditus-queue
audience_whitelist <slug>
user_claims qnr
}
# Token aus der URL einmalig in ein Cookie übernehmen
@rueckkehr query aditus_queue_token=*
header @rueckkehr Set-Cookie "aditus_queue={http.request.uri.query.aditus_queue_token}; Path=/; Secure; HttpOnly; Max-Age=600"
reverse_proxy shop_backend:8080
}
# Ohne gültigen Token: zurück in den Warteraum
handle_errors {
@unauth expression {http.error.status_code} == 401
redir @unauth https://developers.aditus.com/queue/<slug> 302
}
}// Skizze: Cloudflare Worker vor dem Shop — prüft den HS256-JWT offline per
// WebCrypto. Secret als Worker-Secret ADITUS_QUEUE_SECRET hinterlegen.
const SLUG = "<slug>";
const ROOM = "https://developers.aditus.com/queue/" + SLUG;
export default {
async fetch(req, env) {
const url = new URL(req.url);
const fromUrl = url.searchParams.get("aditus_queue_token");
const m = (req.headers.get("Cookie") || "").match(/(?:^|;\s*)aditus_queue=([^;]+)/);
const token = fromUrl || (m && m[1]);
if (!token || !(await valid(token, env.ADITUS_QUEUE_SECRET))) {
return Response.redirect(ROOM, 302); // zurück in den Warteraum
}
if (fromUrl) {
// Token in ein Cookie übernehmen und die URL bereinigen
url.searchParams.delete("aditus_queue_token");
return new Response(null, {
status: 302,
headers: {
Location: url.toString(),
"Set-Cookie": "aditus_queue=" + token +
"; Path=/; Secure; HttpOnly; Max-Age=600",
},
});
}
return fetch(req); // gültiger Pass -> durch zum Shop
},
};
async function valid(token, secret) {
const p = token.split(".");
if (p.length !== 3) return false;
const key = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(secret),
{ name: "HMAC", hash: "SHA-256" }, false, ["verify"],
);
const ok = await crypto.subtle.verify(
"HMAC", key, b64url(p[2]),
new TextEncoder().encode(p[0] + "." + p[1]),
);
if (!ok) return false;
const c = JSON.parse(new TextDecoder().decode(b64url(p[1])));
return c.iss === "aditus-queue" && c.aud === SLUG && c.exp > Date.now() / 1000;
}
function b64url(v) {
return Uint8Array.from(
atob(v.replace(/-/g, "+").replace(/_/g, "/")),
(ch) => ch.charCodeAt(0),
);
}// Skizze: AWS CloudFront mit Lambda@Edge (Viewer-Request, Node.js).
// Gleiches Muster: Token aus ?aditus_queue_token oder Cookie, HS256 offline
// prüfen, ohne gültigen Token in den Warteraum umleiten.
// Hinweis: Lambda@Edge hat keine Umgebungsvariablen — das Secret z. B. beim
// Deployment einbetten oder aus SSM/Secrets Manager cachen.
'use strict';
const crypto = require('crypto');
const SLUG = '<slug>';
const ROOM = 'https://developers.aditus.com/queue/' + SLUG;
const SECRET = '<ADITUS_QUEUE_SECRET>';
exports.handler = async (event) => {
const req = event.Records[0].cf.request;
const qs = new URLSearchParams(req.querystring || '');
const fromUrl = qs.get('aditus_queue_token');
const cookie = (req.headers.cookie || []).map((h) => h.value).join('; ');
const m = cookie.match(/(?:^|;\s*)aditus_queue=([^;]+)/);
const token = fromUrl || (m && m[1]);
if (!token || !valid(token)) {
return redirect(ROOM); // zurück in den Warteraum
}
if (fromUrl) {
// Token in ein Cookie übernehmen und die URL bereinigen
qs.delete('aditus_queue_token');
const clean = req.uri + (qs.toString() ? '?' + qs.toString() : '');
return redirect(clean, {
'set-cookie': [{ key: 'Set-Cookie', value:
'aditus_queue=' + token + '; Path=/; Secure; HttpOnly; Max-Age=600' }],
});
}
return req; // gültiger Pass -> durch zum Shop (Origin)
};
function valid(token) {
const p = token.split('.');
if (p.length !== 3) return false;
const sig = crypto.createHmac('sha256', SECRET)
.update(p[0] + '.' + p[1]).digest();
const got = Buffer.from(p[2], 'base64url');
if (got.length !== sig.length || !crypto.timingSafeEqual(sig, got)) {
return false;
}
const c = JSON.parse(Buffer.from(p[1], 'base64url'));
return c.iss === 'aditus-queue' && c.aud === SLUG && c.exp > Date.now() / 1000;
}
function redirect(location, extraHeaders) {
return {
status: '302',
headers: Object.assign(
{ location: [{ key: 'Location', value: location }] },
extraHeaders || {},
),
};
}Note: with edge enforcement in place, every visitor needs a token — including those who would otherwise pass through silently below the admission rate. Publish the gateway link (option A) alongside it, or let the proxy redirect tokenless visitors to the waiting room, which issues a token immediately while nobody is waiting.
The waiting room is configured by ADITUS. For the setup, provide the following:
A header image for the waiting-room page, e.g. the event key visual. Landscape format, at least 920 × 380 pixels (displayed at up to 460 px width, cropped to a maximum height of 190 px), JPG or PNG, delivered as a publicly reachable HTTPS URL or as a file to ADITUS.
A short message shown below the waiting-room title, e.g. a note about the on-sale or expected wait. Plain text, line breaks are preserved; recommended length up to about 300 characters. The text is displayed exactly as provided — if you serve international visitors, provide it in the appropriate language or bilingually.
A brand color as a hex value (e.g. #0a63c9). It colors the progress bar and the live indicator on the waiting-room page; without a value, the ADITUS default color is used. Together with graphic and text, the waiting room adopts the organizer's branding.
The domains of the pages the script will run on (e.g. shop.example.com). They define which return URLs the waiting room accepts.
The waiting-room title (e.g. the event name), the target URL for admitted visitors (required for the gateway link — it is the shop address the gateway forwards to), and the admission rate in visitors per minute. The rate is the throughput threshold: as long as fewer visitors arrive than the rate allows, everyone passes through without a waiting room. It can be adjusted at any time while the queue is running.
Waiting number — assigned once when the page is opened and kept for the entire wait, even across a reload.
Position and admitted number — how many numbers are ahead and up to which number admission is currently open. The admitted number grows continuously with the admission rate.
Progress bar — visualises how far admission has advanced towards the visitor's own number.
Estimated wait — calculated from the current position and admission rate; it shortens as admission progresses. Once the visitor's number is admitted, the page forwards to the shop automatically — no interaction required.
Header image — optional, e.g. the event key visual.
Display name — the waiting-room title, e.g. the event name.
Message — optional text below the title, shown exactly as provided.
The waiting-room page itself is bilingual (German/English). The language is selected automatically from the visitor's browser setting; no configuration is required.
During the on-sale, ADITUS monitors each waiting room in a live dashboard: gauges show the current waiting count, the inflow of new visitors against the admission rate, the admission rate itself (with its configured ceiling when the self-regulating rate is on) and the estimated wait for newcomers. A two-series chart tracks the waiting count against the inflow over time, and the queue instances serving the traffic report their own load — CPU, memory and request rate. The admission rate can be adjusted at any moment while the queue is running — for example when the shop shows headroom.
The queue is not just claimed to be load-proof — it is tested regularly. From the ADITUS admin console, load tests fire real requests at a dedicated test waiting room on the production system (productive waiting rooms are never touched). A test simulates the visitor path — joining and status polling — at a configurable rate and duration. Live gauges show the achieved request rate, median and p95 latency and the error rate; a per-second chart tracks throughput against latency, and every run is kept in a history so results remain comparable over time. Tests also run on a recurring schedule, and a scheduled run that performs clearly worse than previous comparable runs is flagged automatically.
For external monitoring, the queue service exposes a public health check at /queue/healthz. It answers without authentication and without touching the database — a simple availability signal for uptime monitoring. During a load test, the admin console additionally shows the self-reported queue instances with CPU, memory, event-loop lag and request rate; under load, the platform starts additional instances automatically, and the test makes that visible.
The admission rate is the one dial that decides everything during an on-sale — and until now, someone had to watch it. No longer: the queue can regulate the rate itself, driven by the actual condition of the protected shop. The result is a waiting room that sells as fast as the shop can safely handle — at every moment, without anyone touching a dial.
While the shop is healthy, the rate climbs in small steps and unused headroom turns into sales. The moment the shop shows strain, the rate drops sharply — deliberately asymmetric: relief is instant, load returns carefully.
An unreachable health check never opens the gates wider: the rate freezes first, then falls to the configured minimum. The shop is protected precisely when nobody is looking.
You define what healthy means — response time, CPU load, any metric your shop reports. Every automatic adjustment is recorded and visible in the live dashboard, with the rule that triggered it.
Instead of adjusting the admission rate by hand, a waiting room can regulate it automatically from a health check of the protected shop. ADITUS polls a health URL of the shop at a configurable interval and evaluates the response time and, optionally, numeric fields from the JSON response against operator-defined rules (for example: response time below 500 ms, CPU load below 0.8). While the shop is healthy, the rate is raised slowly in small steps; as soon as a rule is violated, it is lowered quickly by a percentage — deliberately asymmetric, so the shop is relieved immediately but load returns carefully. The rate always stays within a configured minimum and maximum.
If the health endpoint does not respond, the system fails safe: the rate is frozen first, and after several consecutive failures it drops to the configured minimum — an unreachable health check never opens the gates wider. Every automatic adjustment is recorded and visible in the live dashboard, and before the automation can be enabled, the endpoint must pass a one-time test from the admin console.
The health endpoint on the shop side stays trivial: an HTTPS URL, reachable without authentication, answering 2xx. A JSON body is optional — any numeric fields (nested objects are flattened to dot paths) become metrics that rules can reference:
{
"status": "ok",
"responseBudgetMs": 500,
"metrics": {
"cpuLoad": 0.42,
"dbPoolWaiting": 0,
"openCheckouts": 118
}
}
// Nutzbare Messwerte (Punktpfade):
// responseBudgetMs, metrics.cpuLoad, metrics.dbPoolWaiting, metrics.openCheckouts
// Dazu immer verfügbar: die gemessene Antwortzeit (ms).For high-demand on-sales, a waiting room can additionally require a proof of work before a waiting number is issued. The browser then solves a small cryptographic puzzle in the background — invisible to the visitor and, at the default difficulty, well under a second on ordinary devices. For a bot farm the picture changes: every single join costs the same computing work, so drawing thousands of numbers becomes proportionally expensive instead of free. The protection is off by default and enabled per waiting room in the ADITUS admin console; the difficulty is adjustable (8 to 24 bits, default 15 — each additional bit doubles the work). The hosted waiting-room page handles the puzzle automatically; nothing changes for the shop integration.
One deliberate consequence: while the protection is active, the silent pass-through below the admission rate is disabled — every visitor goes through the waiting room, because only there the proof of work is performed. And a note on scope: the proof of work protects the issuing of waiting numbers against mass joining. It is one building block against automated buyers, not a complete bot defense — combine it with measures in the shop itself (such as purchase limits) where needed.
Only custom integrations that call the queue API directly need to solve the puzzle themselves. The flow: fetch a challenge, find a nonce whose SHA-256 hash of challenge plus nonce starts with the required number of zero bits, and submit both with the join request. Challenges are signed and valid for five minutes; reuse is additionally suppressed per instance:
// 1. Challenge holen (nur relevant, wenn PoW für die Queue aktiv ist)
GET https://developers.aditus.com/queue/<slug>/pow
// -> { queue, enabled: true, challenge, bits, expiresInSeconds: 300,
// algorithm: "sha256" }
// 2. Nonce suchen: SHA-256(challenge + "." + nonce) muss mit <bits>
// Null-Bits beginnen. Im Browser per WebCrypto — bei der
// Standard-Schwierigkeit deutlich unter einer Sekunde.
// 3. Beitritt mit Lösung
POST https://developers.aditus.com/queue/<slug>/join
{ "pow": { "challenge": "…", "nonce": "…" } }
// Ohne oder mit ungültiger Lösung antwortet /join mit
// 428 Precondition Required — und liefert im Fehlerkörper direkt eine
// frische Challenge mit, sodass kein zweiter Abruf nötig ist.For announced on-sales, a waiting room can be given a fixed opening time. Visitors arriving early land in a pre-queue: the waiting-room page shows a countdown to the opening, nobody is admitted yet. At the opening time the crucial step happens — the admission order among everyone waiting by then is drawn fairly at random. Arriving three hours early therefore brings no advantage over arriving three minutes early; refresh marathons and camped browser tabs lose their point. Everyone who joins after the opening is placed behind the drawn group in normal arrival order.
The draw is a mathematical permutation over the waiting numbers issued before the opening — every number gets exactly one position, none is lost, none appears twice, and the assignment is not predictable from the joining order. Changing the opening time in the admin console re-arms the draw; nothing changes for the shop integration or the API.
Not everyone who draws a waiting number stays. Closed tabs and abandoned browsers normally leave gaps: the admission window moves over numbers that no longer belong to anyone, and real throughput falls below the configured rate. With abandon detection enabled, the hosted waiting-room page reports its presence in a regular heartbeat. If a number stays silent longer than the configured grace period (60 to 3600 seconds, default 180), it counts as abandoned — and the admission window advances by exactly that amount faster, so vacated slots go to visitors who are actually still waiting.
The detection is deliberately forgiving: a returning visitor whose number was counted as abandoned but is already within the admission window is admitted normally — the heartbeat only accelerates, it never revokes an admission. Custom integrations that build their own waiting page send the same signal via POST /queue/<slug>/heartbeat (JSON body with the waiting number, recommended every 60 seconds).
Press, partners, fan-club contingents: some visitors should never see a waiting room. For them, bypass codes can be created per waiting room in the ADITUS admin console — each with a label, an optional usage limit and an optional expiry date, and each can be disabled or deleted at any time. A code is shared as a simple link: /queue/<slug>/enter?code=… redirects straight to the shop with a valid admission token, past the entire queue. Custom integrations can redeem codes via POST /queue/<slug>/bypass instead and receive the token as JSON.
Two deliberate security properties: an invalid code at the gateway link falls through silently to the normal path — outsiders cannot tell whether a code ever existed. And the JSON route answers identically for invalid, expired, exhausted and disabled codes, so there is no oracle for guessing codes. Bypass admissions are marked as such in the token and do not consume regular admission slots.
The following sections are not required for the integration — the script tag above is complete. They document how the system works underneath for technical evaluation and for shops that want to verify admission in their own backend.
Each visitor is issued a sequential waiting number by a single atomic database increment. Admission advances continuously: the currently admitted number is computed as base + rate × elapsed minutes. Status polling is read-only and does not touch the database per request. There is no per-visitor state on the server, no cron job and no timer — the queue service itself cannot become a bottleneck under load. Pausing a queue stops both admission and the issuing of new numbers; rate changes take effect continuously and the admitted number never decreases.
Admission is strictly first-come-first-served (FCFS): the atomic increment fixes each visitor's position at the moment of joining, and positions never change afterwards. There is deliberately no lottery, no randomization and no priority lane — under scarcity, arrival order is the only ordering visitors accept as fair, and it is the only one that cannot be gamed. Queue jumping is technically excluded on two levels: waiting numbers only ever grow, and the admission check compares the visitor's number against the currently admitted number on the server — the admission token is only issued once that check passes, signed with the queue's secret. A visitor cannot claim a better position, and a forged token fails signature verification. Operators who need separate streams — say, one waiting room per event or per sale — run multiple independent waiting rooms, each with its own slug, secret, rate and configuration.
The design goal follows directly from the admission logic above: because the admitted number is derived from elapsed time and the status check reads no per-visitor state, almost the entire visitor traffic is stateless computation. Any instance can answer any request, instances share nothing but the database, and the platform adds instances automatically under load. The single database write per visitor — the atomic increment at join — is the only serialization point, and it happens exactly once per visitor, not per poll. The numbers below come from the built-in load-test runner described above, measured over the public HTTPS endpoint against a single instance of the queue service; the mix profile combines joining and status polling in the proportion of the real visitor path:
mix · 10 req/s · 30 sLatency p50 7,6 ms · p95 12,0 ms · error rate 0 %mix · 25 req/s · 60 sLatency p50 7,3 ms · p95 11,4 ms · error rate 0 %mix · 50 req/s · 45 sLatency p50 6,7 ms · p95 11,0 ms · error rate 0 %status · 50 req/s · 45 sLatency p50 6,4 ms · p95 8,9 ms · error rate 0 %Measured in July 2026 with the production code path; every run completed with zero errors and single-digit median latency. A single instance handles fifty requests per second — several thousand waiting visitors polling at their recommended interval — without breaking a sweat; beyond that, additional instances take over. This scaling is not a black box: every running instance continuously reports itself with live metrics — CPU, memory, request throughput — visible at any time in the ADITUS admin console, so it is transparent how many instances are carrying an on-sale and how much headroom remains. Scheduled load tests re-verify these numbers continuously against the production system, and a run that falls clearly behind its predecessors is flagged automatically.
Ten endpoints, no authentication for visitors, CORS restricted to the domains configured per queue. The hosted waiting-room page uses exactly this API — it can also be driven directly for a fully custom waiting experience.
GET /queue/<slug>Hosted waiting-room page: requests a number, polls the status and forwards the visitor automatically once admitted.POST /queue/<slug>/joinIssue a waiting number (the only write in the visitor path). 423 while the queue is paused.GET /queue/<slug>/status?number=NPoll the admission status for a number — read-only, includes a Retry-After header. 404 for numbers that were never issued.POST /queue/<slug>/tokenExchange an admitted number for a signed HS256 JWT. 425 while the number is not yet admitted, 404 for numbers that were never issued.GET /queue/<slug>/enterGateway entry point: published instead of the shop URL. Forwards directly to the configured target URL (with admission token) while capacity is available; otherwise redirects into the waiting room. Requires a configured target URL. Accepts ?code=… for VIP bypass codes: a valid code skips the queue entirely, an invalid one falls through to the normal path without any hint.GET /queue/<slug>/embed.jsShop-side integration script: admits visitors silently while nobody is waiting; once a queue has formed it routes visitors without a valid pass into the waiting room and returns them to the exact page they came from.GET /queue/<slug>/verify?token=…Server-side token check used by the integration script: validates signature, audience and expiry of an admission token.GET /queue/<slug>/infoPublic queue info: active flag, currently admitted number and waiting count.POST /queue/<slug>/heartbeatPresence signal for the optional abandon detection: the waiting-room page reports a number as still present. Silently accepted while the detection is off.POST /queue/<slug>/bypassRedeem a VIP bypass code via JSON: returns an admission token plus the target URL. Invalid, expired, exhausted and disabled codes all yield the same 404 — no oracle for code guessing.GET /queue/<slug>/powProof-of-work challenge for the optional bot protection: returns enabled: false while the protection is off; otherwise a signed challenge that /join expects solved.GET /queue/healthzPublic health check for uptime monitoring: answers without authentication and without database access. Deliberately excluded from the instance metrics, so monitoring pings never distort the traffic picture.// Wartenummer anfordern (einziger Schreibzugriff im Besucherpfad).
const res = await fetch("https://developers.aditus.com/queue/<slug>/join", {
method: "POST",
});
const ticket: {
queue: string; // Queue-Slug
name: string; // Anzeigename des Warteraums
number: number; // vergebene Wartenummer
serving: number; // bis zu dieser Nummer wird weitergeleitet
ahead: number; // Anzahl Nummern vor dieser
admitted: boolean; // true -> sofort weiter zu /token
estimatedWaitSeconds: number;
retryAfterSeconds: number; // empfohlenes Polling-Intervall
} = await res.json();
// 423 Locked -> der Warteraum ist pausiert (keine Weiterleitung, keine neuen Nummern).// Statusabfrage — rein lesend, beliebig oft wiederholbar.
// Der Server sendet zusätzlich einen Retry-After-Header.
const res = await fetch(
"https://developers.aditus.com/queue/<slug>/status?number=" + ticket.number,
);
const status: {
queue: string;
name: string;
active: boolean; // false -> Weiterleitung pausiert
number: number;
serving: number;
ahead: number;
admitted: boolean;
estimatedWaitSeconds: number;
retryAfterSeconds: number;
} = await res.json();
if (status.admitted) {
// -> Token abholen und zum Shop weiterleiten
}// Sobald admitted=true: Zugriffs-Token abholen.
const res = await fetch("https://developers.aditus.com/queue/<slug>/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ number: ticket.number }),
});
// 425 Too Early -> die Nummer ist noch nicht zur Weiterleitung freigegeben.
const grant: {
queue: string;
number: number;
token: string; // signierter HS256-JWT
tokenType: "JWT";
expiresInSeconds: number; // Standard: 600 s
targetUrl: string | null; // konfiguriertes Weiterleitungsziel
} = await res.json();
// Weiterleitung übernimmt die gehostete Warteraum-Seite automatisch:
// targetUrl + "?aditus_queue_token=" + grant.tokenThe script-tag integration is complete on its own. Shops that control their backend can additionally verify the admission token there: it is a standard HS256 JWT, verified offline with the shared secret — no callback to the queue, no extra latency. The secret is generated when the queue is created, shown exactly once, and can be rotated at any time. Claims: iss is always aditus-queue, aud is the queue slug, qnr is the admitted waiting number, exp limits validity (default 10 minutes). Note: the silent pass-through under the admission rate does not produce a token — a backend that strictly requires a token should send visitors without one to the waiting room, which then issues a token immediately when nobody is waiting.
// ZIELSYSTEM (dein Shop / deine Seite): Token prüfen — Standard-JWT (HS256).
// Das Secret erhältst du einmalig von ADITUS beim Einrichten des Warteraums.
import jwt from "jsonwebtoken";
const payload = jwt.verify(token, process.env.ADITUS_QUEUE_SECRET!, {
algorithms: ["HS256"],
issuer: "aditus-queue", // iss ist immer aditus-queue
audience: "<slug>", // aud ist der Queue-Slug deines Warteraums
}) as { qnr: number; iat: number; exp: number };
// payload.qnr = die weitergeleitete Wartenummer.
// Gültiger Token -> Besucher passieren lassen (z. B. Cookie setzen).
// Fehlender/ungültiger Token -> zurück in den Warteraum:
// https://developers.aditus.com/queue/<slug>The security mechanisms are described where they belong in the flow above; this section collects them. Signed tokens: admission tokens are HS256 JWTs, signed with a per-queue secret that is shown exactly once and can be rotated at any time; audience binding (queue slug) and a short expiry (default 10 minutes) limit what a captured token is worth. Bot protection: the optional proof-of-work check makes mass-joining from bot farms proportionally expensive; challenges are HMAC-signed and expire after five minutes; a best-effort per-instance guard additionally suppresses reuse of a spent challenge — the real cost driver is the hash work itself. Redirects: return URLs are validated server-side against the domains configured per queue — the waiting room never becomes an open redirect. API surface: visitor endpoints carry no credentials at all, CORS is restricted per queue, admin access uses API keys that are each bound to a single waiting room, stored only as hashes, cannot manage other keys and leave an audit trail. Traffic spikes: the stateless architecture absorbs load by design — any instance answers any request, and the platform scales instances automatically; network-level DDoS filtering is provided by the hosting platform in front of the service.
A waiting room sits in front of revenue — its failure behavior matters as much as its happy path.
ADITUS-Queue addresses the same technical problems as dedicated enterprise virtual-waiting-room products. The table maps the capabilities such products are measured by to how ADITUS-Queue covers them — including one deliberate omission.
Everything the ADITUS admin console does for a single waiting room — adjusting the admission rate, pausing, changing the configuration — is also available to machines. Access uses an API key with the qak_ prefix, sent as a Bearer token; every key is created for exactly one waiting room and can only read and change that room. Keys are created and revoked in the admin console, shown exactly once and stored only as a hash. The typical use case is an on-sale runbook: a scheduled job raises the admission rate right before the start, a script pauses the queue in an emergency, monitoring reads the current configuration. Three guardrails apply: a key never reaches other waiting rooms or global admin functions (load tests, schedules, instances) — such requests are rejected server-side; key management itself is deliberately not possible with a key — a leaked key can never create or list other keys; and every change made with a key is recorded in the audit log under the key's name and its waiting room.
# Konfiguration des zugeordneten Warteraums lesen. Jeder Key ist an genau
# einen Warteraum gebunden — die Liste enthält daher höchstens diesen einen
# Eintrag; andere Warteräume und globale Verwaltungsfunktionen sind mit
# einem Key nicht erreichbar.
curl -H "Authorization: Bearer qak_…" \
https://developers.aditus.com/api/admin/queue/sites
# Weiterleitungsrate im laufenden Betrieb anheben: Lesen -> Feld ändern -> Schreiben.
# PATCH erwartet die vollständige Konfiguration (ohne slug); unbekannte
# Felder wie id oder Zeitstempel werden serverseitig ignoriert.
KEY="qak_…"; BASE="https://developers.aditus.com/api/admin/queue"
SITE=$(curl -s -H "Authorization: Bearer $KEY" "$BASE/sites" \
| jq '.sites[0]')
echo "$SITE" | jq '.ratePerMinute = 300' | curl -s -X PATCH \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d @- "$BASE/sites/$(echo "$SITE" | jq -r .id)"
# Zugeordneten Warteraum pausieren (keine Weiterleitung, keine neuen Nummern):
# active = false
echo "$SITE" | jq '.active = false' | curl -s -X PATCH \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d @- "$BASE/sites/$(echo "$SITE" | jq -r .id)"# .github/workflows/onsale.yml — Weiterleitungsrate zum Vorverkaufsstart anheben.
# Den API-Key als Repository-Secret ADITUS_QUEUE_API_KEY hinterlegen. Der Key
# ist an den Warteraum des Vorverkaufs gebunden — mehr kann er nicht.
name: Weiterleitungsrate zum On-Sale anheben
on:
schedule:
- cron: "55 8 14 3 *" # 14. März, 08:55 UTC — kurz vor dem On-Sale
workflow_dispatch: {}
jobs:
raise-rate:
runs-on: ubuntu-latest
steps:
- name: Rate auf 300/min anheben
env:
KEY: ${{ secrets.ADITUS_QUEUE_API_KEY }}
run: |
BASE="https://developers.aditus.com/api/admin/queue"
# Der Key sieht nur seinen gebundenen Warteraum — .sites[0] genügt.
SITE=$(curl -sf -H "Authorization: Bearer $KEY" "$BASE/sites" \
| jq '.sites[0]')
echo "$SITE" | jq '.ratePerMinute = 300' \
| curl -sf -X PATCH \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d @- "$BASE/sites/$(echo "$SITE" | jq -r .id)"