# チケット発売・限定販売のためのバーチャル待合室

> 販売ページの前に公平でリアルタイムな列を用意します。訪問者は自分の順番が進むのを見ながら、設定したペースで入場し、サイトは署名付きパスを確認します。Laravel や Filament を含め、どのホスティングでも使えます。

Source: https://hibira.io/ja/use-cases/waiting-room

チケットの一般発売、限定アイテムのリリース、予約開始。1 日分のアクセスが販売開始の最初の 1 分に集中します。オートスケールは間に合わず、全員の決済が遅くなり、最後は回線の速さの勝負になります。待合室は、混み合うページの前に落ち着いた公平な列を作ります。訪問者は番号を受け取り、列が進む様子をリアルタイムで見ながら、サイトが処理できるペースで入場します。申し込みも設定もセルフサービスで、営業との商談も、ホスティングや CDN の乗り換えも不要です。

## 訪問者に見えるもの

- 整理番号と「あなたの前に 4,812 人」の表示。待っている間もリアルタイムに更新されます。
- 再読み込みやタブの追加で、順番を失うことも、前に割り込むこともありません。
- 英語、繁体字中国語、簡体字中国語、日本語、韓国語、タイ語、ベトナム語に対応したホスト型待合ページ。
- 販売開始前のわかりやすいカウントダウンと、終了時のはっきりした「完売」表示。

![ホスト型待合ページ：訪問者の順番、前にいる人数、待ち時間の目安、ショップからのお知らせを訪問者の言語で表示します。](https://hibira.io/portal/articles/waitroom-page-ja-light.webp)

## あなたが得られるもの

- 設定したペース（例：1 分あたり 300 人）で進む列。
- 大事な瞬間に使えるボタン：列を一時停止、今すぐ 500 人を入場、在庫がなくなったら終了。
- 入場した訪問者ごとに署名付きのパスを発行。サイトはパスを確認してから保護されたページを返すので、URL を推測しても列は飛ばせません。
- 必要なら、パスのチケット ID を使って 1 つの番号につき決済 1 回に制限できます。
- 列に並んでいる訪問者はアプリの接続数を使わないので、混雑中もサイトの他のリアルタイム機能に余裕が残ります。
- 列に並ぶ全員へのお知らせ（例：「Aエリアは完売」）を、待合ページにすぐ表示できます。

![ポータルの待合室：リアルタイムの人数、入場ペース、列が解消するまでの時間、一時停止・入場・完売・お知らせのボタン。](https://hibira.io/portal/articles/waitroom-console-ja-light.webp)

待合室はどの Hibira プランにも追加できるアドオンです。1 部屋、月 5,000 人の待機訪問者までは無料。有料プランは月 $29 から、単発イベントパスは 72 時間 $49 です。プランの上限を超えた訪問者はクレジットから差し引かれ、誰も締め出されません。詳しくは[料金](https://hibira.io/ja/pricing#waiting-room)をご覧ください。

## 使う機能

- Hibira アプリの**待合室**。ポータルまたは署名付き HTTP API で管理します。
- **ホスト型待合ページ** `ws.hibira.io/wait/APP_KEY/ROOM`、または [@hibira-io/waiting-room](https://www.npmjs.com/package/@hibira-io/waiting-room) で作る独自ページ。
- 部屋ごとの **cache チャンネル**（`cache-waitroom-ROOM`）：1 つのメッセージで待っている全員に列の進み具合を伝え、各ブラウザが自分の順番を計算します。
- アプリの secret で署名した（HMAC-SHA256）**パス**。サイト側で検証でき、Hibira への問い合わせは不要です。

```text
flow: パスを持たない訪問者は列に並び、順番が来たら署名付きパスを持って戻る
訪問者|/drop を開く --パスなし：リダイレクト--> *Hibira|ホスト型待合ページ --順番：`?hibira_pass=…`--> あなたのサイト|パスを確認し cookie を設定
== 列を進める
あなたのチーム|ポータル、Filament、API --開始・一時停止・入場・終了--> *Hibira|cache-waitroom-presale --1 回の更新--> 待っている全員|各自で順番を計算
```

## 接続方法を選ぶ

サイトがどこで動いているかで選びます。上にあるものほど回避されにくくなります。

| サイトの実行環境 | 接続方法 | 確認するタイミング |
| --- | --- | --- |
| Cloudflare Workers、Vercel 上の Next.js、Deno、Bun | [@hibira-io/waiting-room](https://www.npmjs.com/package/@hibira-io/waiting-room) の `protect()` | ページを返す前 |
| 自社サーバー：Express、Laravel など | パスを確認するミドルウェア | ルートの実行前 |
| 完全に独自の待合ページ | ブラウザで `joinWaitingRoom()`、上のいずれかの確認と組み合わせる | ルートの実行前 |
| iOS、Android、Flutter、Unity のアプリ | 待合室の公開エンドポイント、その後 API ヘッダーでパスを送る | 自社 API 内 |
| 自前のバックエンドがない（ホスト型ショップ） | ホスト型待合ページ＋決済時のパス確認 | 決済時 |

## ステップ 1：部屋を作る

ポータルで作るか、イベント送信と同じ方法で署名した HTTP API リクエストで作ります。

```http
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 の middleware、Deno、Bun なら、1 回の呼び出しで済みます。

```js
// 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](https://hibira.io/ja/guides/laravel) 0.4 以降（ブロードキャストですでに使っているかもしれません）なら、コマンドで部屋を作成し、ミドルウェア 1 つでルートを守れます。

```shell
composer require hibira-io/laravel:^0.4
php artisan hibira:waiting-room presale create --rate=300 --title="Winter presale"
```

```php
Route::middleware('hibira.waiting:presale')->group(function () {
    Route::get('/drop', [DropController::class, 'show']);
    Route::post('/drop/checkout', [DropController::class, 'checkout']);
});
```

パスのない訪問者は、アプリと同じ言語の待合ページへ送られます。戻ってきたパスはアプリシークレットで検証され、API 呼び出しは不要です。パスは有効期限まで HttpOnly クッキーに保存されます。パスのない `POST` には `403` が返ります。コントローラーではリクエストからパスを取得できます。

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

部屋はコード、キューのジョブ、コマンドラインから操作できます。

```php
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](https://hibira.io/ja/use-cases/filament) 0.5 以降なら、1 行でパネルに「待合室」ページが追加されます。

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

このページには、部屋ごとの待機人数、列の進み具合、入場ペースがリアルタイムで表示されます。チームはここから一時停止と再開、今すぐ入場、お知らせの掲載、完売時のクローズ、部屋の作成や編集ができます。`authorize` を省略すると、パネルの全ユーザーが開けます。

## ステップ 5：独自の待合ページ

ほとんどのサイトはホスト型ページで十分です。ブランドに完全に合わせたい場合は自作できます。

```js
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')` で確認できます。

```http
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 |
| --- | --- | --- | --- |
| 始め方 | 自分で申し込み、部屋を作成 | 営業に問い合わせ。公開されているのはイベント単位のプラン 1 つ | Business または Enterprise プラン |
| サイトの実行環境 | どのホスティング、CDN、フレームワークでも | どのホスティングでも（同社のコネクタを使用） | Cloudflare を経由するプロキシが必要 |
| 開始時刻の予約とカウントダウン | あり | あり | Enterprise |
| 複数の部屋 | あり | あり | Enterprise＋Advanced アドオン |
| 独自の待合ページ | あり（軽量なブラウザクライアント） | あり | Enterprise のカスタムテンプレート |
| 自社の管理画面から列を操作 | 可能（例：Filament のページ） | 同社のダッシュボード | Cloudflare のダッシュボードまたは API |

2026 年 11 月時点の各社の公開価格・プランページに基づきます。プランは変わることがあるため、決める前に各社に確認してください。

価格とそれぞれが向いているケースを含む詳しい比較：[Queue-it](https://hibira.io/ja/compare/queue-it)、[Cloudflare Waiting Room](https://hibira.io/ja/compare/cloudflare-waiting-room)、[CrowdHandler](https://hibira.io/ja/compare/crowdhandler)、[Queue-Fair](https://hibira.io/ja/compare/queue-fair)。

## ヒント

- 本当に売り切れるページを守りましょう：商品ページ、カート、決済。トップページや画像は待合室の外に置きます。
- ブラウザだけでなく、重要なリクエストでパスを確認してください。訪問者は自分のページのスクリプトを書き換えられますが、署名は偽造できません。
- 1 つの番号で 1 回だけ購入させたい場合は、決済時にパスのチケット ID を記録し、同じ ID での 2 回目の注文を拒否します。
- 販売の 15〜30 分前に部屋を開け、`starts_at` を設定しましょう。早く来た訪問者はサイトで再読み込みを連打する代わりに、カウントダウンで待ちます。
- まずは決済が余裕を持って処理できるペースで始め、エラー率を見ながら上げていきます。一時停止はいつでもワンクリックです。
