把第三方 webhook 推到使用者正在看的頁面
付款確認、包裹移動、建置完成:把 Stripe、物流或 GitHub 的 webhook 轉到使用者正在看的頁面,不必重新整理。
顧客在 Stripe Checkout 付款後回到你的「感謝」頁面,但付款是由 webhook 確認的,可能晚幾秒才到。頁面顯示「正在確認付款…」,顧客只好重新整理,甚至再付一次。物流、CI 服務、電子簽章工具和電商平台也是一樣:消息送到的是你的伺服器,而不是使用者正在看的頁面。用 Hibira,webhook 處理程式一收到更新,就把它轉給那個頁面。
使用者得到什麼
- 在正在看的頁面上直接出現「已收到付款」,不必重新整理。
- 物流、建置和簽署進度自己出現。
- 不會再有人以為第一次失敗而重複付款。
用到的功能
- 私人頻道(
private-order-42),只有訂單本人可以加入,由你的授權端點檢查。 - 在驗證服務商簽章並儲存變更的同一個 webhook 處理程式裡呼叫
trigger。 pusher:subscription_succeeded,用來讀一次目前狀態,頁面打開前就到的 webhook 也不會漏掉。
步驟 1:儲存後轉發 webhook
驗證簽章、略過已處理過的事件(服務商會重試)、儲存新狀態,再發布:
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);
});步驟 2:只讓本人收聽
由你的頻道授權端點決定誰可以訂閱:
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));
});步驟 3:頁面上收聽,並讀一次狀態
先訂閱,訂閱就緒時再讀一次目前狀態,這樣不管 webhook 比頁面早到還是晚到都沒關係:
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);
});其他 webhook 也是同一個做法
| 來源 | Webhook | 打開的頁面顯示 |
|---|---|---|
| Stripe、PayPal、Adyen | 付款成功或失敗 | 收據,或重試按鈕 |
| 物流業者 | 追蹤更新 | 訂單頁上的新進度 |
| GitHub、GitLab、Vercel | 建置或部署完成 | 狀態標籤和預覽連結 |
| 電子簽章工具 | 文件已簽署 | 「雙方已簽署」 |
| Shopify、電商平台 | 訂單成立或已付款 | 賣家儀表板上多一列 |