티켓 오픈과 한정 판매를 위한 대기실
판매 페이지 앞에 공정한 실시간 대기열을 둡니다. 방문자는 순서가 줄어드는 것을 보며 설정한 속도로 입장하고, 사이트는 서명된 패스를 확인합니다. Laravel과 Filament를 포함해 어떤 호스팅에서도 사용할 수 있습니다.
티켓 오픈, 한정판 드롭, 사전 예약 시작. 하루 치 트래픽이 판매 시작 첫 1분에 몰립니다. 오토스케일링은 그렇게 빨리 반응하지 못해 모두의 결제가 느려지고, 결국 회선 속도 싸움이 됩니다. 대기실은 붐비는 페이지 앞에 차분하고 공정한 줄을 세웁니다. 방문자는 번호를 받고, 줄이 줄어드는 것을 실시간으로 보며, 사이트가 감당할 수 있는 속도로 입장합니다. 가입과 설정은 직접 하면 되고, 영업 상담도 호스팅이나 CDN 이전도 필요 없습니다.
방문자가 보는 것
- 대기 번호와 "앞에 4,812명", 기다리는 동안 실시간으로 갱신됩니다.
- 새로고침하거나 탭을 더 열어도 순서를 잃거나 앞당길 수 없습니다.
- 영어, 번체 중국어, 간체 중국어, 일본어, 한국어, 태국어, 베트남어를 지원하는 호스팅 대기 페이지.
- 판매 전에는 명확한 카운트다운, 끝나면 분명한 "매진" 표시.

여러분이 얻는 것
- 설정한 속도(예: 분당 300명)로 움직이는 줄.
- 중요한 순간에 쓰는 버튼: 줄 일시 정지, 지금 500명 입장, 재고가 떨어지면 종료.
- 입장한 방문자마다 서명된 패스를 발급하고, 사이트가 이를 확인한 뒤 보호된 페이지를 보여 주므로 URL을 추측해도 새치기할 수 없습니다.
- 원한다면 패스의 티켓 ID로 번호 하나당 결제 한 번만 허용할 수 있습니다.
- 줄을 선 방문자는 앱의 연결 수를 쓰지 않으므로, 몰리는 동안에도 사이트의 다른 실시간 기능은 여유를 유지합니다.
- 줄을 선 모든 사람에게 공지(예: "A구역 매진")를 보내면 대기 페이지에 바로 표시됩니다.

대기실은 모든 Hibira 요금제에 추가할 수 있는 애드온입니다. 방 1개와 월 5,000명의 대기 방문자까지 무료이고, 유료 요금제는 월 $29부터, 단발 이벤트 패스는 72시간 $49입니다. 요금제 한도를 넘는 방문자는 크레딧에서 차감되며 아무도 돌려보내지 않습니다. 자세한 내용은 요금을 보세요.
사용하는 기능
- Hibira 앱의 대기실. 포털이나 서명된 HTTP API로 관리합니다.
- 호스팅 대기 페이지
ws.hibira.io/wait/APP_KEY/ROOM, 또는 @hibira-io/waiting-room으로 만드는 자체 페이지. - 방마다 하나의 cache 채널(
cache-waitroom-ROOM): 메시지 하나로 기다리는 모든 방문자에게 줄이 어디까지 왔는지 알리고, 각 브라우저가 자기 순서를 계산합니다. - 앱 secret으로 서명한(HMAC-SHA256) 패스. 사이트에서 직접 확인하며 Hibira를 호출할 필요가 없습니다.
?hibira_pass=…줄 움직이기
연결 방법 고르기
사이트가 어디서 실행되는지에 맞춰 고르세요. 목록에서 위에 있을수록 우회하기 어렵습니다.
| 사이트 실행 환경 | 연결 방법 | 확인 시점 |
|---|---|---|
| Cloudflare Workers, Vercel의 Next.js, Deno, Bun | @hibira-io/waiting-room의 protect() | 페이지를 보내기 전 |
| 자체 서버: Express, Laravel 등 | 패스를 확인하는 미들웨어 | 라우트 실행 전 |
| 완전히 직접 만든 대기 페이지 | 브라우저의 joinWaitingRoom()과 위 확인 방법 중 하나 | 라우트 실행 전 |
| iOS, Android, Flutter, Unity 앱 | 대기실 공개 엔드포인트, 이후 API 헤더에 패스 전달 | 여러분의 API에서 |
| 자체 백엔드가 없음(호스팅 쇼핑몰) | 호스팅 대기 페이지 + 결제 시 패스 확인 | 결제 시 |
1단계: 방 만들기
포털에서 만들거나, 이벤트 발행과 같은 방식으로 서명한 HTTP API 요청으로 만듭니다.
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은 설정한 속도로 입장시키고, paused는 줄을 멈추고, closed는 매진 시 번호 발급을 중단합니다. starts_at 전에 온 방문자는 번호를 받고 카운트다운을 보며 기다리며, 그 시각에 줄이 움직이기 시작합니다.
2단계: 엣지에서 페이지 보호하기
Cloudflare Workers, Next.js 미들웨어, Deno, Bun에서는 호출 한 번이면 됩니다.
// 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*'] };패스가 없는 방문자는 대기 페이지로 보내집니다. 차례가 되면 패스를 들고 돌아오고, protect()가 확인한 뒤 HttpOnly cookie에 저장합니다. Express에서는 같은 패키지의 expressWaitingRoom()을 사용하세요.
3단계: Laravel
hibira-io/laravel 0.4 이상(이미 브로드캐스팅에 쓰고 있을 수도 있습니다)이라면 명령어로 방을 만들고 미들웨어 하나로 라우트를 보호합니다.
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']);
});패스가 없는 방문자는 앱과 같은 언어의 대기 페이지로 이동합니다. 돌아올 때 가져온 패스는 앱 시크릿으로 API 호출 없이 검증되고, 만료될 때까지 HttpOnly 쿠키에 저장됩니다. 패스 없는 POST는 403을 받습니다. 컨트롤러에서는 요청에서 패스를 꺼낼 수 있습니다.
$pass = $request->attributes->get('hibira_pass');
$pass->number; // queue number
$pass->ticketId; // record it at checkout: one order per number방은 코드, 큐 작업, 명령줄에서 운영할 수 있습니다.
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();로컬 개발에서는 HIBIRA_WAITING_ROOMS=false로 설정하면 줄을 건너뜁니다.
4단계: Filament에서 줄 운영하기
판매 당일 팀이 또 다른 대시보드를 열 필요가 없습니다. hibira-io/filament 0.5 이상이라면 한 줄로 패널에 '대기실' 페이지가 추가됩니다.
HibiraPlugin::make()->waitingRooms(
authorize: fn ($user) => $user->is_admin,
)이 페이지는 방마다 대기 인원, 줄의 진행 정도, 입장 속도를 실시간으로 보여 줍니다. 팀은 여기서 일시 정지와 재개, 즉시 입장, 공지 게시, 매진 시 방 닫기, 방 만들기와 수정을 할 수 있습니다. authorize를 빼면 패널 사용자 모두가 들어올 수 있습니다.
5단계: 직접 만든 대기 페이지
대부분의 사이트는 호스팅 페이지로 충분합니다. 브랜드에 정확히 맞추고 싶다면 직접 만드세요.
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(),
});클라이언트가 방문자의 티켓을 브라우저에 저장하므로 새로고침해도 순서가 유지됩니다. 연결을 열 수 없으면 몇 초마다 상태를 확인하는 방식으로 바뀌어 아무도 막히지 않습니다.
6단계: 앱 안에서
모바일이나 게임 클라이언트는 호스팅 페이지와 같은 공개 엔드포인트를 씁니다. 줄에 들어가고, 방의 cache 채널을 구독하고, 번호가 되면 패스를 받아 이후 API 호출에 함께 보냅니다. API는 앱 시크릿으로 패스를 확인하며 Hibira를 호출할 필요가 없습니다. Laravel에서는 WaitingRoom::verifyPass($pass, 'presale')로 확인합니다.
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": "…"}Queue-it, Cloudflare Waiting Room과 비교
| Hibira | Queue-it | Cloudflare Waiting Room | |
|---|---|---|---|
| 시작 방법 | 직접 가입하고 방 만들기 | 영업 문의, 공개된 이벤트 단위 상품 하나 | Business 또는 Enterprise 요금제 |
| 사이트 실행 환경 | 어떤 호스팅, CDN, 프레임워크든 | 어떤 호스팅이든(자사 커넥터 사용) | Cloudflare 프록시를 거쳐야 함 |
| 예약 시작과 카운트다운 | 있음 | 있음 | Enterprise |
| 여러 개의 방 | 있음 | 있음 | Enterprise + Advanced 애드온 |
| 직접 만든 대기 페이지 | 있음, 작은 브라우저 클라이언트로 | 있음 | Enterprise 커스텀 템플릿 |
| 자체 관리자 화면에서 줄 운영 | 가능, 예: Filament 페이지 | 자사 대시보드 | Cloudflare 대시보드 또는 API |
2026년 11월 기준 각사의 공개 요금 및 요금제 페이지를 바탕으로 했습니다. 요금제는 바뀔 수 있으니 결정 전에 각사에 확인하세요.
가격과 각각이 더 맞는 경우를 담은 자세한 비교: Queue-it, Cloudflare Waiting Room, CrowdHandler, Queue-Fair.
팁
- 실제로 매진되는 페이지를 보호하세요: 상품 페이지, 장바구니, 결제. 홈페이지와 이미지는 대기실 밖에 두세요.
- 브라우저에서만이 아니라 중요한 요청에서 패스를 확인하세요. 방문자는 자기 페이지의 스크립트를 고칠 수 있어도 서명은 위조할 수 없습니다.
- 번호 하나당 한 번만 구매하게 하려면 결제 시 패스의 티켓 ID를 기록하고, 같은 ID로 들어온 두 번째 주문을 거절하세요.
- 판매 15~30분 전에 방을 열고
starts_at을 설정하세요. 일찍 온 방문자는 사이트에서 새로고침을 연타하는 대신 카운트다운에서 기다립니다. - 결제가 여유 있게 처리할 수 있는 속도로 시작하고, 오류율을 보면서 올리세요. 일시 정지는 언제나 클릭 한 번입니다.