← Use cases

Waiting rooms for ticket drops and flash sales

Put a fair, live line in front of a sale: visitors see their place move, come in at the rate you set, and your site checks a signed pass. Any host, Laravel and Filament included.

A ticket release, a limited drop or a pre-order opening brings a whole day's traffic in the first minute. Autoscaling can't react that fast, so checkout slows down for everyone and the fastest connection wins. A waiting room puts a calm, fair line in front of the busy pages: visitors take a number, watch the line move live, and come in at the rate your site can serve. You sign up and set one up yourself, with no sales call and no change of host or CDN.

What your visitors get

  • A queue number and "4,812 people ahead of you", updated live while they wait.
  • A place that a refresh or a second tab doesn't lose or improve.
  • A hosted waiting page in English, Traditional and Simplified Chinese, Japanese, Korean, Thai and Vietnamese.
  • A clear countdown before the sale starts, and a clear "sold out" when it ends.
The hosted waiting page: the visitor's place in line, people ahead, the estimated wait and your notice, in the visitor's language.
The hosted waiting page: the visitor's place in line, people ahead, the estimated wait and your notice, in the visitor's language.

What you get

  • A line that moves at the rate you set, for example 300 visitors a minute.
  • Buttons for the moments that matter: pause the line, let 500 in now, close it when stock runs out.
  • A signed pass for each admitted visitor that your site checks before serving the protected page, so nobody skips the line by guessing a URL.
  • One check-out per queue number if you want it, using the pass's ticket ID.
  • Visitors waiting in line don't use your app's connections, so the realtime features on the rest of your site keep their room during the rush.
  • A notice for everyone in line, such as "Zone A is sold out", shown on the waiting page at once.
Waiting rooms in the portal: live counts, the rate, when the line clears, and buttons to pause, let people in, mark sold out or post a notice.
Waiting rooms in the portal: live counts, the rate, when the line clears, and buttons to pause, let people in, mark sold out or post a notice.

Waiting rooms are an add-on to any Hibira plan. One room and 5,000 queued visitors a month are free; paid plans start at $29 a month, and a one-off event pass is $49 for 72 hours. Past your plan, extra visitors come from credits and nobody is turned away. See pricing.

Built with

  • Waiting rooms in your Hibira app, managed from the portal or with signed HTTP API calls.
  • The hosted waiting page, ws.hibira.io/wait/APP_KEY/ROOM, or your own page with @hibira-io/waiting-room.
  • A cache channel per room (cache-waitroom-ROOM): one message tells every waiting visitor how far the line has moved, and each browser works out its own position.
  • Passes signed with your app secret (HMAC-SHA256), checked by your site with no call to Hibira.
Visitoropens /drop
no pass: redirect
Hibirahosted waiting page
their turn: ?hibira_pass=…
Your sitechecks the pass, sets a cookie

Moving the line

Your teamportal, Filament or API
open, pause, admit, close
Hibiracache-waitroom-presale
one update
Every waiting visitorworks out its own position
Visitors without a pass wait in line; when their number comes up they return with a signed pass

Choose how to connect

Pick the one that matches where your site runs. The higher in the list, the harder it is to get around.

Step 1: create a room

Create the room in the portal, or with a signed request to the HTTP API, signed the same way as publishing an event:

POST /apps/APP_ID/waiting_rooms/presale
{
  "admit_per_minute": 300,
  "state": "paused",
  "starts_at": "2026-12-04T12:00:00+09:00",
  "title": "Winter tour presale",
  "return_urls": ["https://shop.example.com/"]
}

open lets people in at the rate, paused holds the line, and closed stops handing out numbers when you're sold out. Visitors who arrive before starts_at get a number and a countdown; the line starts moving at that time.

Step 2: protect the page at the edge

On Cloudflare Workers, Next.js middleware, Deno or Bun, one call does the whole job:

// middleware.js (Next.js)
import { NextResponse } from 'next/server';
import { protect } from '@hibira-io/waiting-room/server';

export async function middleware(request) {
  const blocked = await protect(request, {
    appKey: process.env.HIBIRA_KEY,
    secret: process.env.HIBIRA_SECRET,
    room: 'presale',
  });
  return blocked ?? NextResponse.next();
}

export const config = { matcher: ['/drop/:path*'] };

A visitor without a pass is sent to the waiting page. When it's their turn they come back with a pass, which protect() checks and keeps in an HttpOnly cookie. On Express, use expressWaitingRoom() from the same package.

Step 3: Laravel

With hibira-io/laravel 0.4 or later, which you may already use for broadcasting, create the room from the command line and protect your routes with one middleware:

composer require hibira-io/laravel:^0.4
php artisan hibira:waiting-room presale create --rate=300 --title="Winter presale"
Route::middleware('hibira.waiting:presale')->group(function () {
    Route::get('/drop', [DropController::class, 'show']);
    Route::post('/drop/checkout', [DropController::class, 'checkout']);
});

A visitor without a pass is sent to the waiting page, in your app's language. The pass they bring back is checked with your app secret, with no API call, and kept in an HttpOnly cookie until it expires. A POST without a pass gets a 403. In your controller, the pass is on the request:

$pass = $request->attributes->get('hibira_pass');
$pass->number;   // queue number
$pass->ticketId; // record it at checkout: one order per number

Run the room from code, a queued job or the command line:

use Hibira\Laravel\Facades\Hibira;

Hibira::waitingRoom('presale')->pause();
Hibira::waitingRoom('presale')->admit(500);
Hibira::waitingRoom('presale')->notice('Zone A is sold out');
Hibira::waitingRoom('presale')->close();

Set HIBIRA_WAITING_ROOMS=false in local development to skip the line.

Step 4: run the line from Filament

Your team doesn't need another dashboard on sale day. With hibira-io/filament 0.5 or later, one line adds a Waiting rooms page to your panel:

HibiraPlugin::make()->waitingRooms(
    authorize: fn ($user) => $user->is_admin,
)

The page shows each room's waiting count, how far the line has got and the rate, updated live. From it your team can pause and open the line, let people in now, post a notice, close the room when stock runs out, and create or edit rooms. Leave out authorize to let every panel user in.

Step 5: your own waiting page

The hosted page fits most sites. To match your brand exactly, build the page yourself:

import { joinWaitingRoom } from '@hibira-io/waiting-room';

joinWaitingRoom({
  key: 'APP_KEY',
  room: 'presale',
  onUpdate: ({ ahead, etaMinutes }) => render(ahead, etaMinutes),
  onTurn: (turn) => turn.redirect('https://shop.example.com/drop'),
  onClosed: () => showSoldOut(),
});

The client keeps the visitor's ticket in the browser, so a refresh keeps their place. If it can't open a connection, it checks the status every few seconds instead, and nobody is locked out.

Step 6: in your app

A mobile or game client uses the same public endpoints as the hosted page: it joins, follows the room's cache channel, asks for a pass when its number comes up, and sends the pass with its API calls. Your API checks the pass with your app secret, with no call to Hibira; on Laravel, WaitingRoom::verifyPass($pass, 'presale') does it.

POST /waitroom/APP_KEY/presale/join         -> {"ticket": "…", "number": 5121}
GET  /waitroom/APP_KEY/presale              -> {"admitted": 4980, "state": "open", …}
POST /waitroom/APP_KEY/presale/pass         {"ticket": "…"}  -> {"pass": "…"}

Compared with Queue-it and Cloudflare Waiting Room

Based on their public pricing and plan pages in November 2026. Plans change, so check with each vendor before you decide.

Full comparisons, with prices and when each one fits better: Queue-it, Cloudflare Waiting Room, CrowdHandler and Queue-Fair.

Tips

  • Protect the pages that actually run out: the product page, the cart and checkout. Leave the home page and your images outside the room.
  • Check the pass on the request that matters, not only in the browser. A visitor can edit any script on their own page; they can't forge a signature.
  • If each queue number may buy once, record the pass's ticket ID at checkout and refuse a second order with it.
  • Open the room 15 to 30 minutes before the sale and set starts_at. Early visitors wait on the countdown instead of hitting refresh on your site.
  • Start with a rate your checkout can comfortably serve, then raise it while you watch your error rates. Pausing is always one click away.