把第三方 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、电商平台 | 订单创建或已付款 | 卖家仪表盘上多一行 |