Queue-it's architecture: the queue token, the cookie, and safety-net mode
A virtual waiting room has an awkward job. It has to keep millions of people out of an origin that would fall over if they all arrived at once, and it has to do that without the origin’s application code knowing much about how the queue works. The origin shouldn’t have to talk to the queue on every request. It shouldn’t have to trust the visitor. And it should be able to tell, in microseconds, whether the person knocking on the door already stood in line or is trying to walk straight in.
Queue-it solves this with two signed artifacts and a small piece of code that runs in front of the origin. The first artifact is a token that rides back on the URL when a visitor’s turn comes up. The second is a cookie the origin’s connector mints after it checks that token, so the visitor doesn’t have to re-prove themselves on every page. Both are HMAC-signed with a secret only the customer and Queue-it know, which is what makes the whole thing unskippable when it’s deployed correctly. This post is about how those pieces fit together, drawn from Queue-it’s open-source connector code and its own documentation, and it stays on the architecture rather than on how to walk through it as a client (that lives in the companion field guide).
We’ll start with the connector model and the four places it can run. Then the queue token: its field layout, how it’s parsed, and how the signature is checked. Then the QueueITAccepted cookie: what it stores, how it’s signed differently from the token, and how validity gets extended. Then the backend that actually issues the tokens, the FIFO machinery and the throughput control behind it. Then safety-net mode and triggers, which decide whether anyone queues at all. And finally where the design’s seams are.
The connector is the whole security boundary
Queue-it’s product is hosted. The waiting room itself (the page that shows your number, the polling, the ordering) runs entirely on Queue-it’s infrastructure, mostly on AWS. The customer’s origin never renders a queue page and never holds queue state. What the customer runs is a connector: a small shim that sits on the request path, decides whether an incoming request is allowed through, and if not, sends it to Queue-it with a 302.
That shim is the entire trust boundary. Everything Queue-it can promise about being unskippable comes down to the connector running somewhere the visitor can’t tamper with, and checking a signature the visitor can’t forge. Queue-it ships the connector in four shapes, and they differ mostly in where the code physically runs.
The lightest mode is simple-link: the operator just points visitors at a Queue-it-hosted URL and the waiting room handles everything, returning the visitor to the origin with a token appended. If the origin never verifies that token, there’s no connector at all, only a redirect. The client-side JavaScript mode drops a script tag on protected pages; the script asks Queue-it whether to queue and redirects the browser if so. Queue-it’s own docs are candid that this is “less secure than our server-side or edge Connectors, as tech-savvy end-users can potentially manipulate the client code and skip the queue.” The validation runs in a place the visitor owns.
The two modes that matter for security are the server-side connector and the edge connector. The server-side connector is an open-source library (Queue-it publishes KnownUser implementations for Node.js, PHP, Python, Java, Ruby, .NET, Lua, and others) that runs inside the origin application and gates requests before they reach business logic. The edge connector is the same logic compiled for a CDN’s worker runtime: Cloudflare Workers, AWS Lambda@Edge on CloudFront, Fastly, Akamai EdgeWorkers, plus Google Cloud Service Extensions, Tencent Cloud, and F5. The edge version is the more interesting one architecturally, because it rejects unqueued requests at the CDN edge, often a long way from the origin, and the origin never even sees them.
The property of both that makes them work at scale is that the per-request decision is local. Queue-it’s Akamai EdgeWorkers connector validates the request “locally inside the edge worker and there is no communication with the Queue-it backend during this process.” The connector doesn’t phone home on every hit. It holds the customer’s secret key and the integration config, and it checks the signature itself. That’s the only way to put a queue in front of a flash sale without the queue’s own validation path becoming the bottleneck you were trying to avoid.
The queue token: what rides back on the URL
When a visitor’s turn comes up, Queue-it sends them back to the origin with a token appended to the URL as a query-string parameter named queueittoken. This is the proof that they cleared the line. The connector’s first job on a returning request is to find that parameter, parse it, and check that it’s genuine.
The token is not opaque. It’s a flat, tilde-delimited string of labelled fields followed by a hash. You can read its structure directly out of the Fastly VCL connector, which parses it with a regular expression. The primary form the connector matches is:
e_{eventId}~q_{queueId}~ts_{timestamp}~ce_{extendable}~cv_{version}~rt_{redirectType}~h_{hash}Each field has a two-letter prefix and a tilde separator. e_ carries the event (waiting-room) id. q_ is the visitor’s queue id, the unique identifier tied to their place in line. ts_ is the expiry timestamp, the moment after which this token is no longer good. ce_ is the cookie-extendable flag. cv_ is a cookie-validity value. rt_ is the redirect type, which records how the visitor arrived (whether they came through the queue, were let through by the safety-net’s idle behaviour, or were disabled). h_ is the HMAC. The connector also matches a fallback form of the regex that omits the cv_ field, for tokens minted by older or differently-configured backends.
~h_ is signed; the hash is HMAC-SHA256 of that prefix under the customer’s secret key. No field is encrypted; the signature is what stops forgery.
Validation is straightforward, and the connector code makes the steps explicit. The Fastly connector computes digest.hmac_sha256(table.lookup(queueit_config, "Secret_key"), var.token_wo_hash), an HMAC-SHA256 over the token string with the trailing ~h_... removed, keyed by the customer’s secret. It compares that to the supplied hash. The PHP KnownUser library does the same thing in different words: hash_hmac('sha256', $queueParams->queueITTokenWithoutHash, $secretKey), compared case-insensitively against the hash in the token.
The order of checks matters, because the connector reports why a token failed and uses that reason to decide where to send the visitor. The PHP UserInQueueService returns a TokenValidationResult with one of a small set of reasons. If the hash doesn’t match, the reason is "hash". If $queueParams->timeStamp < time(), meaning the token’s ts_ is in the past, the reason is "timestamp". If the event id baked into the token doesn’t match the event the connector is configured for, the reason is "eventid". The Fastly connector mirrors this with a Queue-IT-Error header set to "timestamp" when std.time(var.expires, 0s) < now. On any of these failures the visitor isn’t quietly let in and isn’t hard-blocked; they’re sent back to Queue-it with the broken token attached, so the backend can decide whether to re-queue them or show an error.
The secret key is the linchpin. Queue-it’s documentation describes it as a 72-character secret configured in the GO Queue-it self-service platform and shared with the connector. Because the same key signs every token for a customer and lives in the connector’s config, the security of the entire deployment reduces to that key staying secret and the connector actually performing the check. A connector that parses the token but skips the HMAC comparison (or an origin running simple-link mode that never verifies at all) is wide open, and that’s a configuration failure rather than a flaw in the token format. The format itself is fine: a plaintext field set with a keyed MAC over it is a perfectly ordinary signed-token design, the same shape as a lot of capability URLs. What it is not is a bearer secret you can keep private, since by design it travels in the open on a URL where the visitor can read every field except recompute the hash.
The QueueITAccepted cookie: turning one token into a session
If the connector had to see a valid queueittoken on every single request, the visitor would lose their pass the moment they clicked a second link, because the token lives on the URL and the URL changes. So after the connector validates the queue token once, it mints a cookie. On every subsequent request the connector checks the cookie instead of the token, and the visitor browses normally.
That cookie is QueueITAccepted-SDFrts345E-V3_{eventId}. The fixed middle chunk, SDFrts345E-V3, is a constant; you can see it hard-coded in the connector source as const _QueueITDataKey = "QueueITAccepted-SDFrts345E-V3";. The per-event suffix means a visitor can hold valid sessions for several waiting rooms at once, one cookie each, without them colliding. The cookie is set on the customer’s own domain (the protected origin), not on Queue-it’s domain, because it’s the origin’s connector that reads it.
The cookie’s value carries EventId, QueueId, a FixedCookieValidityMinutes value, RedirectType, IssueTime, and Hash. The hash is computed by generateHash, which in the PHP repository is:
private function generateHash($eventId, $queueId, $fixedCookieValidityMinutes,$redirectType, $issueTime, $secretKey) { return hash_hmac('sha256', $eventId . $queueId . $fixedCookieValidityMinutes . $redirectType . $issueTime, $secretKey);}So the cookie is signed with the same secret key as the token but over a different string: a concatenation of the cookie’s own fields rather than the token’s. That separation is deliberate. It means a captured queueittoken hash can’t simply be pasted into a cookie, and vice versa, because the two MACs cover different inputs. The cookie also stores its own IssueTime, which lets the connector reason about freshness independently of the token’s ts_.
Cookie lifetime has two modes, and the IsCookieExtendable flag (the token’s ce_ field) chooses between them. In the extendable mode the cookie has a sliding window: each time the connector validates it on a request, it re-issues the cookie with a fresh IssueTime, so an active visitor keeps their session alive by browsing. The connector’s cookieValidityMinute and extendCookieValidity settings (in the PHP example, cookieValidityMinute = 15 with extendCookieValidity = true) control this window. In the fixed mode, signalled by a non-empty FixedCookieValidityMinutes, the session is hard-capped from issue time regardless of activity, which is what you want for a ticket on-sale where you don’t want a session that stays alive indefinitely just because someone keeps clicking. On edge connectors the extension is wired to the response phase: Akamai EdgeWorkers does the request-time validation in onClientRequest and uses onClientResponse “to update (extend) the cookie session validity” after the origin has answered.
There’s one more cookie family worth naming, because it shows up in the cookie statement and gets confused with the session cookie. On Queue-it’s own waiting-room domain (not the origin), the backend sets fault-tolerance cookies: Queue-it-{queueId} and Queue-it-{customerId}_{eventId} hold backup copies of the visitor’s queue id and visitor id, Queue-it-token is a backup of the connector token in case it’s lost from the redirect URL, Queue-it-visitorsession maintains state while the visitor is sitting in the waiting room, and queueitsoftblock_{queueId} stores a CAPTCHA hash when a visitor has solved a challenge to get into the queue. None of these gate the origin. Only the QueueITAccepted-SDFrts345E-V3_{eventId} cookie does that, and only it lives on the customer’s domain.
What the connector does on a single request
Put the token and the cookie together and the connector’s per-request logic is a short decision tree. The entry point in the server-side libraries is KnownUser.ValidateRequestByIntegrationConfig(), which takes the request and an integration config (the triggers and actions, more on those below) and returns a result telling the host application either to continue or to redirect.
The connector first asks whether the request even targets something protected, by evaluating the triggers in the integration config against the URL, headers, and cookies. If nothing matches, the request is none of Queue-it’s business and flows straight through. If a trigger matches, the connector looks for a valid QueueITAccepted cookie for that event. A good cookie means continue, and in extendable mode the cookie is re-issued on the way out. No cookie, but a valid queueittoken on the URL, means the visitor just came back from the queue: validate the token’s HMAC and expiry, mint the cookie, strip the token from the URL, and continue. Neither a cookie nor a valid token means the visitor hasn’t been through the line, and the connector returns a 302 to the waiting room with the event and customer ids in the redirect.
The whole tree resolves without contacting Queue-it. The connector holds the secret key and the cached integration config, and every decision (trigger match, cookie HMAC, token HMAC, expiry) is a local computation. That’s what lets it sit in a Cloudflare Worker or a Lambda@Edge function and add only the cost of a couple of HMACs to each request. The backend gets involved only when the connector hands a visitor off to the queue, which is the case the system is built to make rare.
The backend behind the token: FIFO at flash-sale scale
The connector is the gate. The interesting distributed-systems problem is on the other side: actually running the line and deciding, moment to moment, whose turn it is to receive a token. Queue-it’s engineers have described this system in their own podcast, and it’s worth getting the shape right because it explains why the token has the fields it does.
The waiting room maintains FIFO ordering across what the team describes as millions of requests per minute, and it does so as a distributed system rather than a single ordered list. As one of their engineers put it, “It’s a distributed system. It can scale really fast. It’s fault-tolerant.” Many nodes accept visitors, assign queue numbers, and report their local traffic. The nodes then coordinate on an “open window”: a range of queue numbers “allowed to get redirected in the next 10 seconds, or whatever that interval is.” A visitor whose number falls inside the current window is eligible to be handed a token and sent back to the origin. The coordination backbone is DynamoDB, which the team singles out for being able to “handle a couple hundred thousand TPS,” with Lambda@Edge, ALB, ECS/EC2, SQS, SNS, and WAF filling out the rest of the stack.
The control problem is outflow, not inflow. The customer declares a capacity, say 500 requests per minute, and the distributed nodes coordinate to release token-bearing visitors at roughly that rate. The wrinkle is no-shows: some fraction of visitors who get the green light never actually click through to the origin, so a system that released exactly 500 numbers would deliver fewer than 500 real arrivals. The backend compensates by adjusting the window to keep actual outflow near the target. This is the same token-bucket-and-position machinery that virtual waiting rooms share in general; the token-buckets-and-fair-ordering primer covers the family, and the fair-queue-at-scale piece covers what breaks when you push it. What’s specific to Queue-it here is that the released visitor’s identity is bound into the queue token (the q_ queue id and the e_ event id) and signed, so the connector on the far end can confirm “the person that started the journey is the person that lands in the connector” without the backend and the connector having to share live session state. The token is the only thing that crosses the gap, and the signature is what makes it trustworthy.
It’s a clean separation. The backend owns ordering and rate; the connector owns admission. They communicate through a signed, expiring, single-event token and never need a synchronous call between them on the hot path. Compare that to Cloudflare’s Waiting Room, which folds both halves into the same edge platform and uses a JWT, or to Akamai’s CDN-edge queueing; Queue-it’s design keeps the queue as a separate hosted service that any origin or any CDN can defer to, which is why it ends up in front of so many different stacks.
Safety-net mode and triggers: deciding who waits at all
Most of the time, on most protected sites, nobody should be queued. The whole point of safety-net mode is that the queue is dormant until traffic actually threatens the origin. Queue-it describes it plainly: with a safety net, “if traffic inflow exceeds the thresholds you configure, only then will the online queue activate, and visitors would be placed in the waiting room in a standard first-in, first-out order.” Below the threshold, requests sail through with no waiting room at all. The connector is still running and still evaluating every request; it’s just that the action it takes is “continue” until the backend flips the event into active queueing.
That flip is governed by triggers and actions, the rule layer configured in the GO Queue-it platform. A trigger is a condition evaluated against the request, and Queue-it exposes a useful set of attributes to match on: request URL, request body, user agent, cookies, HTTP headers, and (in client-side mode) JavaScript variables. An action says what to do when a trigger fires, typically which waiting room should protect the matched pages. The integration config bundles these rules, and once configured they’re pushed to the connector. The edge connectors cache this config and evaluate it locally, which is how a Cloudflare Worker can decide a request matches a trigger without asking anyone.
There’s a meaningful design point hiding in the trigger attributes. Because triggers can match on user agent and headers, the same mechanism that protects against a legitimate traffic spike doubles as a coarse bot control: an operator can write a trigger that always sends requests with certain header signatures, or to certain high-value paths, into the waiting room regardless of inflow. That’s not a fingerprinting engine in the sense of DataDome or Akamai Bot Manager; Queue-it is a queue, not a bot-detection vendor, and its matching is rule-based rather than ML-scored. But in a flash-sale context the queue is itself a powerful anti-automation tool, because it imposes a serialised, rate-limited, signed-token admission process that a naive scraper can’t shortcut. The hard part for an automated client was never forging the token (that’s cryptographically blocked); it was enduring the wait and carrying the cookie correctly, which is exactly the architecture this post has been describing. For deployments that want real bot scoring on top, Queue-it sits alongside a dedicated vendor rather than replacing one, and the queue’s CAPTCHA softblock (queueitsoftblock_{queueId}) is its only built-in challenge.
One subtlety about thresholds: safety-net activation is a property of the backend, not the connector. The connector keeps evaluating triggers and checking cookies the same way whether the event is dormant or active. What changes when inflow crosses the threshold is that the backend stops handing out tokens immediately on demand and starts metering them through the open-window mechanism, so visitors who reach the queue actually wait instead of being bounced straight back with a fresh token. From the connector’s point of view nothing special happens at the threshold; it’s the queue page behaviour and the token issuance rate that change. This is why safety net is cheap to leave on year-round: a dormant queue costs the connector two HMACs per request and the backend almost nothing.
Where the seams are
The design is coherent, and most of what looks like a weakness is really a configuration surface. The token is plaintext, but it’s signed, so reading it tells an attacker the field layout and nothing forgeable. The secret key signs both the token and the cookie, but over different strings, so the two artifacts don’t interchange. The connector decides locally, which is fast but means the security depends entirely on the connector being present and actually checking. The modes where it isn’t present (simple-link with no origin verification, client-side JS that the visitor controls) are documented by Queue-it as the weaker options, and they’re weaker for exactly the reason the architecture predicts: the check has moved to a place the visitor owns.
The seams that remain are the ones inherent to any signed-token gate. A token is a bearer credential for its lifetime, so a token captured before its ts_ expiry is replayable until then, and the FixedCookieValidityMinutes versus extendable choice is really a knob on how long a leaked session stays useful. The single per-customer secret means one key compromise unlocks token minting for every event that customer runs, which is why the connector config that holds it deserves the same care as any signing key. And because the queue and the connector communicate only through the token, anything the backend wants the connector to enforce has to fit in the token’s fields; the format’s small fixed vocabulary (e_ q_ ts_ ce_ cv_ rt_ h_) is the entire channel between the two halves of the system. The general pattern of where these systems lose tokens, why waiting rooms leak, applies to Queue-it as much as anyone: the failures are almost always reuse and races around the token’s lifetime, not breaks in the HMAC.
What’s notable, reading the connector code in 2026, is how little the core has changed. The QueueITAccepted-SDFrts345E-V3 data key, the tilde-delimited token, the HMAC-SHA256 over a plain concatenation: these are the same primitives Queue-it has shipped for years, ported faithfully across a dozen languages and four CDN edge runtimes. The product’s growth has been outward (more edge platforms, more languages, safety net as a default posture) rather than a rework of the token. For a system whose only job is to be trusted by an origin that can’t afford to think hard on the hot path, a small, stable, signed format that every connector implements identically is the right kind of boring.
Sources & further reading
- Queue-it (2024), How Queue-it Works — the integration types, the triggers-and-actions framework, and the signed-token model in Queue-it’s own words.
- Queue-it (2024), Connectors — the taxonomy of server-side, edge, and client-side connectors and the security trade-offs Queue-it states between them.
- Queue-it (2024), Cookie Statement for End-Users — exact names of the QueueITAccepted session cookie and the waiting-room backup/softblock cookies, with their domains and purposes.
- queueit (GitHub), KnownUser.Fastly Connector.vcl — the token regex, the
e_ q_ ts_ ce_ cv_ rt_ h_fields, thedigest.hmac_sha256validation, and the timestamp check, all readable in VCL. - queueit (GitHub), KnownUser.V3.PHP UserInQueueStateCookieRepository.php — the
_QueueITDataKeyconstant and thegenerateHashfunction showing the cookie’s signed concatenation. - queueit (GitHub), KnownUser.V3.PHP UserInQueueService.php — token validation, the
hash/timestamp/eventidfailure reasons, and the SDK version string. - queueit (GitHub), KnownUser.V3.PHP — the README’s
cookieValidityMinute,extendCookieValidity, and the 72-character secret-key requirement. - Queue-it (2024), Virtual Waiting Room System Design (Smooth Scaling podcast, ep. 17) — the distributed FIFO, the open-window outflow control, no-show compensation, and the DynamoDB backbone, from Queue-it’s engineers.
- AWS Partner Network (2024), How to manage peak traffic on AWS using Queue-it’s virtual waiting room — the Lambda@Edge ViewerRequest/ViewerResponse flow, the 302 redirect to the waiting room, and the signed-token return.
- Queue-it (2023), Introducing the Akamai EdgeWorkers Connector — the
onClientRequest/onClientResponsesplit and the statement that request validation happens locally with no backend round-trip. - Queue-it (2023), Use Edge Computing to Integrate Securely & Easily — where an edge connector runs in the request lifecycle and why edge/server-side enforcement removes the client-side bypass surface.
Further reading
How to bypass Queue-it: a field guide for HTTP clients in 2026
What a virtual waiting room actually does, what an HTTP client has to handle to walk through it the way a browser would, and the five layers any client needs to model correctly.
·14 min readInside the DataDome JS tag: what ddjskey and the client payload carry
A reference on DataDome's client-side JavaScript tag: the ddjskey site identifier, the signals the browser collector gathers and posts to api-js.datadome.co, and how the challenge and interstitial flow is wired.
·21 min readAkamai Bot Manager's _abck cookie: structure, sensor data, and the validation handshake
Traces what the _abck cookie carries, how it relates to the sensor_data POST, and the handshake that flips it from a challenge state to a validated one, with notes on what is documented versus inferred.
·21 min read