# Phòng chờ cho mở bán vé và flash sale

> Đặt một hàng chờ công bằng, cập nhật trực tiếp trước trang mở bán: khách thấy vị trí của mình tiến lên, vào theo tốc độ bạn đặt, và trang của bạn kiểm tra pass có chữ ký. Dùng được với mọi hosting, kể cả Laravel và Filament.

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

Mở bán vé, ra mắt sản phẩm giới hạn hay mở đặt trước: lưu lượng của cả một ngày dồn vào phút đầu tiên. Tự động mở rộng không phản ứng kịp, thanh toán chậm đi với tất cả mọi người, và cuối cùng ai có mạng nhanh nhất sẽ thắng. Phòng chờ đặt một hàng chờ bình tĩnh, công bằng trước những trang đông người: khách lấy số, xem hàng tiến lên theo thời gian thực, và vào theo tốc độ mà trang của bạn phục vụ được. Bạn tự đăng ký và tự thiết lập, không cần gọi cho bộ phận bán hàng, không cần đổi hosting hay CDN.

## Khách truy cập nhận được gì

- Số thứ tự và dòng "còn 4.812 người phía trước", cập nhật trực tiếp trong lúc chờ.
- Tải lại trang hay mở thêm tab cũng không làm mất chỗ hay chen lên trước.
- Trang chờ được lưu trữ sẵn bằng tiếng Anh, tiếng Trung phồn thể, tiếng Trung giản thể, tiếng Nhật, tiếng Hàn, tiếng Thái và tiếng Việt.
- Đếm ngược rõ ràng trước giờ mở bán, và thông báo "đã bán hết" rõ ràng khi kết thúc.

![Trang chờ lưu trữ sẵn: vị trí trong hàng, số người phía trước, thời gian chờ ước tính và thông báo của cửa hàng, bằng ngôn ngữ của khách.](https://hibira.io/portal/articles/waitroom-page-vi-light.webp)

## Bạn nhận được gì

- Một hàng chờ tiến lên theo tốc độ bạn đặt, ví dụ 300 khách mỗi phút.
- Các nút cho những lúc quan trọng: tạm dừng hàng chờ, cho 500 người vào ngay, đóng khi hết hàng.
- Mỗi khách được vào nhận một pass có chữ ký, trang của bạn kiểm tra trước khi phục vụ trang được bảo vệ, nên đoán URL cũng không chen hàng được.
- Nếu muốn, dùng ticket ID trong pass để mỗi số thứ tự chỉ thanh toán được một lần.
- Khách đang xếp hàng không tính vào số kết nối của ứng dụng, nên các tính năng realtime khác trên trang vẫn còn chỗ trong lúc cao điểm.
- Thông báo cho mọi người đang xếp hàng, ví dụ "Khu A đã hết vé", hiện ngay trên trang chờ.

![Phòng chờ trong portal: số người trực tiếp, tốc độ cho vào, thời gian hết hàng chờ, cùng các nút tạm dừng, cho vào, hết hàng và thông báo.](https://hibira.io/portal/articles/waitroom-console-vi-light.webp)

Phòng chờ là tiện ích bổ sung cho mọi gói Hibira. Miễn phí 1 phòng và 5.000 khách xếp hàng mỗi tháng; gói trả phí từ $29 mỗi tháng, và vé sự kiện một lần là $49 trong 72 giờ. Vượt quá gói, khách thêm được trừ vào tín dụng và không ai bị từ chối. Xem [bảng giá](https://hibira.io/vi/pricing#waiting-room).

## Dùng những gì

- **Phòng chờ** trong ứng dụng Hibira của bạn, quản lý trên portal hoặc bằng các lệnh gọi HTTP API có chữ ký.
- **Trang chờ được lưu trữ sẵn** `ws.hibira.io/wait/APP_KEY/ROOM`, hoặc trang của riêng bạn với [@hibira-io/waiting-room](https://www.npmjs.com/package/@hibira-io/waiting-room).
- Mỗi phòng một **cache channel** (`cache-waitroom-ROOM`): một tin nhắn cho mọi khách đang chờ biết hàng đã đi đến đâu, và mỗi trình duyệt tự tính vị trí của mình.
- **Pass** ký bằng app secret của bạn (HMAC-SHA256), trang của bạn tự kiểm tra mà không cần gọi Hibira.

```text
flow: Khách chưa có pass phải xếp hàng; đến lượt thì quay lại với pass có chữ ký
Khách|mở /drop --không có pass: chuyển hướng--> *Hibira|trang chờ lưu trữ sẵn --đến lượt: `?hibira_pass=…`--> Trang của bạn|kiểm tra pass, đặt cookie
== Cho hàng chờ tiến lên
Đội của bạn|portal, Filament hoặc API --mở, tạm dừng, cho vào, đóng--> *Hibira|cache-waitroom-presale --một lần cập nhật--> Mọi khách đang chờ|tự tính vị trí
```

## Chọn cách kết nối

Chọn theo nơi trang của bạn đang chạy. Càng ở trên trong danh sách thì càng khó lách.

| Trang chạy ở đâu | Kết nối thế nào | Kiểm tra lúc nào |
| --- | --- | --- |
| Cloudflare Workers, Next.js trên Vercel, Deno, Bun | `protect()` từ [@hibira-io/waiting-room](https://www.npmjs.com/package/@hibira-io/waiting-room) | trước khi trả trang |
| Máy chủ của bạn: Express, Laravel và các framework khác | một middleware kiểm tra pass | trước khi route chạy |
| Trang chờ tự làm hoàn toàn | `joinWaitingRoom()` trong trình duyệt, cùng một trong các cách kiểm tra ở trên | trước khi route chạy |
| Ứng dụng iOS, Android, Flutter hoặc Unity | các endpoint công khai của phòng chờ, rồi gửi pass trong header của API | trong API của bạn |
| Không có backend riêng (cửa hàng được lưu trữ sẵn) | trang chờ lưu trữ sẵn, cộng kiểm tra pass khi thanh toán | khi thanh toán |

## Bước 1: tạo phòng

Tạo phòng trên portal, hoặc bằng một yêu cầu HTTP API có chữ ký, ký giống như khi gửi sự kiện:

```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` cho khách vào theo tốc độ, `paused` giữ hàng chờ đứng yên, và `closed` ngừng phát số khi bạn đã bán hết. Khách đến trước `starts_at` sẽ nhận số và xem đếm ngược; hàng chờ bắt đầu tiến lên vào đúng thời điểm đó.

## Bước 2: bảo vệ trang ở edge

Trên Cloudflare Workers, middleware của Next.js, Deno hoặc Bun, chỉ cần một lệnh gọi:

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

Khách không có pass được chuyển đến trang chờ. Khi đến lượt, họ quay lại với pass, `protect()` kiểm tra và lưu nó trong cookie HttpOnly. Với Express, dùng `expressWaitingRoom()` trong cùng gói.

## Bước 3: Laravel

Với [hibira-io/laravel](https://hibira.io/vi/guides/laravel) 0.4 trở lên (có thể bạn đã dùng cho broadcasting), tạo phòng bằng dòng lệnh và bảo vệ route bằng một 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']);
});
```

Khách chưa có pass được chuyển tới trang chờ, theo ngôn ngữ của ứng dụng. Pass họ mang về được kiểm tra bằng app secret của bạn, không cần gọi API, và lưu trong cookie HttpOnly tới khi hết hạn. Một `POST` không có pass nhận `403`. Trong controller, pass nằm trên request:

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

Điều khiển phòng từ code, job trong hàng đợi hoặc dòng lệnh:

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

Khi phát triển trên máy, đặt `HIBIRA_WAITING_ROOMS=false` để bỏ qua hàng chờ.

## Bước 4: điều khiển hàng chờ từ Filament

Ngày mở bán, đội của bạn không cần thêm một dashboard nữa. Với [hibira-io/filament](https://hibira.io/vi/use-cases/filament) 0.5 trở lên, một dòng thêm trang "Phòng chờ" vào panel:

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

Trang này hiện trực tiếp số người đang chờ, hàng đã đi tới đâu và tốc độ cho vào của từng phòng. Từ đây đội của bạn có thể tạm dừng và mở lại, cho người vào ngay, đăng thông báo, đóng phòng khi hết hàng, và tạo hoặc sửa phòng. Bỏ `authorize` thì mọi người dùng panel đều vào được.

## Bước 5: trang chờ của riêng bạn

Trang lưu trữ sẵn phù hợp với hầu hết các trang web. Muốn khớp hoàn toàn với thương hiệu, hãy tự làm trang:

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

Client lưu vé của khách trong trình duyệt, nên tải lại trang vẫn giữ chỗ. Nếu không mở được kết nối, nó chuyển sang kiểm tra trạng thái vài giây một lần, và không ai bị chặn ở ngoài.

## Bước 6: trong ứng dụng của bạn

Client di động hay game dùng cùng các endpoint công khai với trang lưu trữ sẵn: vào hàng, theo dõi cache channel của phòng, xin pass khi đến số của mình, rồi gửi pass kèm các lệnh gọi API. API của bạn kiểm tra pass bằng app secret, không cần gọi Hibira; trên Laravel, dùng `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": "…"}
```

## So với Queue-it và Cloudflare Waiting Room

|  | Hibira | Queue-it | Cloudflare Waiting Room |
| --- | --- | --- | --- |
| Bắt đầu thế nào | Tự đăng ký và tự tạo phòng | Liên hệ bộ phận bán hàng; có một gói theo sự kiện công khai giá | Gói Business hoặc Enterprise |
| Trang chạy ở đâu | Bất kỳ hosting, CDN hay framework nào | Bất kỳ hosting nào, qua connector của họ | Lưu lượng phải đi qua proxy của Cloudflare |
| Hẹn giờ mở với đếm ngược | Có | Có | Enterprise |
| Nhiều phòng | Có | Có | Enterprise, kèm add-on Advanced |
| Trang chờ của riêng bạn | Có, với một client trình duyệt nhỏ gọn | Có | Template tùy chỉnh của Enterprise |
| Điều khiển hàng chờ từ trang quản trị của bạn | Có, ví dụ một trang Filament | Bảng điều khiển của họ | Bảng điều khiển hoặc API của Cloudflare |

Dựa trên trang giá và gói công khai của từng bên vào tháng 11/2026. Các gói có thể thay đổi, hãy kiểm tra với từng bên trước khi quyết định.

So sánh đầy đủ, kèm giá và khi nào mỗi bên phù hợp hơn: [Queue-it](https://hibira.io/vi/compare/queue-it), [Cloudflare Waiting Room](https://hibira.io/vi/compare/cloudflare-waiting-room), [CrowdHandler](https://hibira.io/vi/compare/crowdhandler) và [Queue-Fair](https://hibira.io/vi/compare/queue-fair).

## Mẹo

- Bảo vệ những trang thật sự có thể hết hàng: trang sản phẩm, giỏ hàng và thanh toán. Để trang chủ và hình ảnh ở ngoài phòng chờ.
- Kiểm tra pass ở yêu cầu quan trọng, không chỉ trong trình duyệt. Khách có thể sửa mọi script trên trang của họ, nhưng không giả được chữ ký.
- Nếu mỗi số thứ tự chỉ được mua một lần, hãy ghi lại ticket ID của pass khi thanh toán và từ chối đơn thứ hai dùng cùng ID.
- Mở phòng 15 đến 30 phút trước giờ bán và đặt `starts_at`. Khách đến sớm sẽ chờ ở trang đếm ngược thay vì bấm tải lại liên tục trên trang của bạn.
- Bắt đầu với tốc độ mà khâu thanh toán phục vụ thoải mái, rồi tăng dần trong khi theo dõi tỉ lệ lỗi. Tạm dừng luôn chỉ cách một cú nhấp.
