# 搶票與限量開賣的排隊等候室

> 在開賣頁前排一條公平、即時的隊伍：訪客看著自己的位置前進，依你設定的速度進場，你的網站驗證簽名 pass。任何主機都能用，包括 Laravel 與 Filament。

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

搶票、限量發售、預購開跑，一整天的流量會擠在開賣的第一分鐘。自動擴展來不及反應，結帳對每個人都變慢，最後拼的是誰的網速快。等候室在最忙的頁面前排一條平靜、公平的隊伍：訪客拿到號碼、即時看著隊伍前進，依你的網站撐得住的速度進場。自己註冊、自己設定，不用找業務，也不用換主機或 CDN。

## 訪客會看到什麼

- 排隊號碼與「前面還有 4,812 人」，等待期間即時更新。
- 重新整理或多開分頁，位置不會掉，也不會往前插隊。
- 託管等候頁支援英文、繁體中文、簡體中文、日文、韓文、泰文與越南文。
- 開賣前有清楚的倒數，賣完時清楚顯示「已售完」。

![託管等候頁：訪客的排隊位置、前面人數、預估等待時間與商家公告，以訪客的語言顯示。](https://hibira.io/portal/articles/waitroom-page-zh-TW-light.webp)

## 你會得到什麼

- 依你設定的速度前進的隊伍，例如每分鐘 300 人。
- 關鍵時刻用得到的按鈕：暫停隊伍、立刻放 500 人進來、庫存賣完就關閉。
- 每位放行的訪客都會拿到簽名 pass，你的網站驗證後才提供受保護的頁面，猜網址也插不了隊。
- 需要的話，用 pass 裡的 ticket ID 做到一個號碼只能結帳一次。
- 排隊中的訪客不佔用 app 的連線數，搶購期間網站其他即時功能照常有餘裕。
- 對所有排隊的人發公告，例如「A 區已售完」，等候頁立刻顯示。

![主控台裡的等候室：即時人數、放行速度、預估清空時間，以及暫停、放行、售罄與公告按鈕。](https://hibira.io/portal/articles/waitroom-console-zh-TW-light.webp)

等候室是任何 Hibira 方案都能加購的附加功能。一個房間、每月 5,000 位排隊訪客免費；付費方案每月 $29 起，單場活動票 $49 可用 72 小時。超過方案用量時，多出的訪客從點數扣抵，不會有人被擋在門外。詳見[價格](https://hibira.io/zh-tw/pricing#waiting-room)。

## 用到的功能

- Hibira app 裡的**等候室**，在主控台或用簽名的 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`）：一則訊息告訴所有等候中的訪客隊伍走到哪裡，每個瀏覽器自己算出位置。
- 用你的 app secret 簽名（HMAC-SHA256）的 **pass**，你的網站自己驗證，不必呼叫 Hibira。

```text
flow: 沒有 pass 的訪客先排隊，輪到時帶著簽名 pass 回來
訪客|開啟 /drop --沒有 pass：導向--> *Hibira|託管等候頁 --輪到了：`?hibira_pass=…`--> 你的網站|驗證 pass、設定 cookie
== 讓隊伍前進
你的團隊|主控台、Filament 或 API --開啟、暫停、放行、關閉--> *Hibira|cache-waitroom-presale --一則更新--> 所有等候中的訪客|各自算出位置
```

## 選擇接入方式

依網站跑在哪裡來選。越上面的方式越難被繞過。

| 網站跑在哪裡 | 怎麼接入 | 在哪裡檢查 |
| --- | --- | --- |
| Cloudflare Workers、Vercel 上的 Next.js、Deno、Bun | [@hibira-io/waiting-room](https://www.npmjs.com/package/@hibira-io/waiting-room) 的 `protect()` | 頁面送出之前 |
| 自己的伺服器：Express、Laravel 等 | 一個驗證 pass 的 middleware | 路由執行之前 |
| 完全自訂的等候頁 | 瀏覽器端的 `joinWaitingRoom()`，再搭配上面任一種檢查 | 路由執行之前 |
| iOS、Android、Flutter 或 Unity App | 等候室的公開端點，再把 pass 放在 API header | 你的 API 裡 |
| 沒有自己的後端（託管商店） | 託管等候頁，加上結帳時檢查 pass | 結帳時 |

## 步驟 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：在 edge 保護頁面

在 Cloudflare Workers、Next.js middleware、Deno 或 Bun 上，一個呼叫就搞定：

```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*'] };
```

沒有 pass 的訪客會被導到等候頁。輪到時帶著 pass 回來，`protect()` 驗證後存進 HttpOnly cookie。Express 請用同一個套件裡的 `expressWaitingRoom()`。

## 步驟 3：Laravel

使用 [hibira-io/laravel](https://hibira.io/zh-tw/guides/laravel) 0.4 以上版本（你可能已經用它做廣播），用指令建立房間，再用一個middleware 保護路由：

```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']);
});
```

沒有 pass 的訪客會被導到等候頁，語言跟你的 app 一致。訪客帶回來的 pass用你的 app secret 驗證，不需要呼叫 API，並存在 HttpOnly cookie 裡直到過期。沒有 pass 的 `POST` 會得到 `403`。在 controller 裡可以從 request 取得 pass ：

```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/zh-tw/use-cases/filament) 0.5 以上版本，一行就能在後台加上「等候室」頁面：

```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(),
});
```

用戶端會把訪客的 ticket 存在瀏覽器裡，重新整理也不會掉位置。如果連不上，它會改成每隔幾秒查一次狀態，不會有人被擋在外面。

## 步驟 6：在 App 裡排隊

手機或遊戲用戶端用的是和託管等候頁相同的公開端點：加入隊伍、訂閱房間的 cache 頻道、輪到時換 pass，之後在 API 呼叫中帶上 pass。你的 API 用 app secret 驗證 pass，不需要呼叫 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 |
| --- | --- | --- | --- |
| 怎麼開始 | 自己註冊、自己建立房間 | 聯絡業務；有一個公開的單場方案 | 需要 Business 或 Enterprise 方案 |
| 網站跑在哪裡 | 任何主機、CDN 或框架 | 任何主機，搭配他們的 connector | 流量要經過 Cloudflare 代理 |
| 排程開賣與倒數 | 有 | 有 | Enterprise |
| 多個房間 | 有 | 有 | Enterprise，加購 Advanced |
| 自訂等候頁 | 有，用小巧的瀏覽器用戶端 | 有 | Enterprise 自訂範本 |
| 在自己的後台操作隊伍 | 可以，例如 Filament 頁面 | 他們的後台 | Cloudflare 後台或 API |

依據 2026 年 11 月各家公開的價格與方案頁面。方案會變動，做決定前請再向各家確認。

完整比較（含價格與各自適合的情況）：[Queue-it](https://hibira.io/zh-tw/compare/queue-it)、[Cloudflare Waiting Room](https://hibira.io/zh-tw/compare/cloudflare-waiting-room)、[CrowdHandler](https://hibira.io/zh-tw/compare/crowdhandler)、[Queue-Fair](https://hibira.io/zh-tw/compare/queue-fair)。

## 小提醒

- 保護真正會賣完的頁面：商品頁、購物車與結帳。首頁和圖片放在等候室外面。
- 在關鍵的請求上驗證 pass，不要只在瀏覽器檢查。訪客可以改自己頁面上的任何腳本，但偽造不了簽名。
- 如果一個號碼只能買一次，結帳時記下 pass 的 ticket ID，拒絕用同一個 ID 下第二筆訂單。
- 開賣前 15 到 30 分鐘就開啟房間並設定 `starts_at`。早到的訪客會在倒數頁等，而不是在你的網站狂按重新整理。
- 一開始先用結帳撐得住的速度，一邊看錯誤率一邊往上調。暫停永遠只差一個按鈕。
