← Tình huống

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.

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.
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.

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.
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.

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á.

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.
  • 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.
Kháchmở /drop
không có pass: chuyển hướng
Hibiratrang chờ lưu trữ sẵn
đến lượt: ?hibira_pass=…
Trang của bạnkiểm tra pass, đặt cookie

Cho hàng chờ tiến lên

Đội của bạnportal, Filament hoặc API
mở, tạm dừng, cho vào, đóng
Hibiracache-waitroom-presale
một lần cập nhật
Mọi khách đang chờtự tính vị trí
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ý

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.

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:

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:

// 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 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:

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

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:

$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:

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 0.5 trở lên, một dòng thêm trang "Phòng chờ" vào panel:

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:

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').

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

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, Cloudflare Waiting Room, CrowdHandler và 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.