Đưa webhook bên thứ ba tới trang người dùng đang mở
Thanh toán đã xác nhận, kiện hàng di chuyển, build xong: chuyển webhook từ Stripe, đơn vị vận chuyển hay GitHub tới trang người dùng đang xem, không cần tải lại.
Khách thanh toán trên Stripe Checkout rồi quay lại trang "Cảm ơn" của bạn, nhưng việc xác nhận thanh toán đến qua webhook và có thể trễ vài giây. Trang hiện "Đang xác nhận thanh toán…", khách tải lại trang, hoặc tệ hơn là trả tiền lần nữa. Đơn vị vận chuyển, dịch vụ CI, công cụ ký điện tử và sàn thương mại cũng vậy: tin tức đến máy chủ của bạn, chứ không đến trang người dùng đang xem. Với Hibira, trình xử lý webhook chuyển cập nhật tới trang đó ngay khi nhận được.
Người dùng nhận được gì
- "Đã nhận thanh toán" hiện ngay trên trang đang xem, không cần tải lại.
- Cập nhật vận chuyển, build và chữ ký tự xuất hiện.
- Không còn ai trả tiền hai lần vì tưởng lần đầu bị lỗi.
Xây dựng bằng
- Kênh riêng tư (
private-order-42) mà chỉ chủ đơn hàng được tham gia, do endpoint cấp quyền của bạn kiểm tra. triggerngay trong trình xử lý webhook đã xác minh chữ ký của nhà cung cấp và lưu thay đổi.pusher:subscription_succeededđể đọc trạng thái hiện tại một lần, nên webhook đến trước khi trang mở cũng không bị bỏ lỡ.
Bước 1: chuyển tiếp webhook sau khi lưu
Xác minh chữ ký, bỏ qua sự kiện đã xử lý (nhà cung cấp sẽ gửi lại), lưu trạng thái mới, rồi phát:
app.post('/stripe/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET);
if (await db.webhookEvents.seen(event.id)) return res.sendStatus(200);
if (event.type === 'checkout.session.completed') {
const orderId = event.data.object.metadata.orderId;
await db.orders.update(orderId, { status: 'paid' });
await pusher.trigger(`private-order-${orderId}`, 'status', { status: 'paid' });
}
res.sendStatus(200);
});Bước 2: chỉ cho chủ đơn lắng nghe
Endpoint cấp quyền kênh của bạn quyết định ai được đăng ký:
app.post('/pusher/auth', requireLogin, express.urlencoded({ extended: false }), async (req, res) => {
const id = req.body.channel_name.match(/^private-order-(.+)$/)?.[1];
if (!id || !(await db.orders.ownedBy(id, req.user.id))) return res.sendStatus(403);
res.json(pusher.authorizeChannel(req.body.socket_id, req.body.channel_name));
});Bước 3: lắng nghe trên trang và đọc trạng thái một lần
Đăng ký trước, rồi đọc trạng thái hiện tại khi đăng ký sẵn sàng, để webhook đến trước hay sau khi trang tải cũng không sao:
const channel = pusher.subscribe('private-order-42');
channel.bind('status', ({ status }) => show(status));
channel.bind('pusher:subscription_succeeded', async () => {
const order = await fetch('/api/orders/42').then((r) => r.json());
show(order.status);
});Các webhook khác cũng làm như vậy
| Nguồn | Webhook | Trang đang mở hiển thị |
|---|---|---|
| Stripe, PayPal, Adyen | thanh toán thành công hoặc thất bại | biên nhận, hoặc nút thử lại |
| Đơn vị vận chuyển | cập nhật hành trình | bước mới trên trang đơn hàng |
| GitHub, GitLab, Vercel | build hoặc deploy xong | huy hiệu trạng thái và link xem trước |
| Công cụ ký điện tử | tài liệu đã ký | "Hai bên đã ký" |
| Shopify, sàn thương mại | đơn được tạo hoặc thanh toán | dòng mới trên dashboard người bán |
Mẹo
- Trả 200 cho nhà cung cấp thật nhanh. Việc chậm hãy làm sau khi phản hồi hoặc trong tác vụ nền, xem tiến độ cho tác vụ dài.
- Chỉ chuyển những gì trang cần (trạng thái và ID), đừng chuyển payload gốc của nhà cung cấp: nó có thể chứa dữ liệu cá nhân.
- Nếu người dùng đã đóng trang, gửi thông báo đẩy theo hướng dẫn thông báo đẩy.
- Với dashboard của cả nhóm, dùng một kênh cho mỗi nhóm thay vì mỗi đơn hàng.