← 使用场景

把第三方 webhook 推到用户正在看的页面

付款确认、包裹移动、构建完成:把 Stripe、物流或 GitHub 的 webhook 转到用户正在看的页面,无需刷新。

顾客在 Stripe Checkout 付款后回到你的「感谢」页面,但付款是由 webhook 确认的,可能晚几秒才到。页面显示「正在确认付款…」,顾客只好刷新,甚至再付一次。物流、CI 服务、电子签名工具和电商平台也是一样:消息送到的是你的服务器,而不是用户正在看的页面。用 Hibira,webhook 处理程序一收到更新,就把它转给那个页面。

用户得到什么

  • 在正在看的页面上直接出现「已收到付款」,不必刷新。
  • 物流、构建和签署进度自己出现。
  • 不会再有人以为第一次失败而重复付款。

用到的功能

  • 私有频道(private-order-42),只有订单本人可以加入,由你的授权接口检查。
  • 在验证服务商签名并保存变更的同一个 webhook 处理程序里调用 trigger。
  • pusher:subscription_succeeded,用来读一次当前状态,页面打开前就到的 webhook 也不会漏掉。
浏览器你的服务器StripeHibira在 Checkout 付款跳回 /orders/42「正在确认付款…」webhookcheckout.session.completedtrigger("status")private-order-42status: paid显示收据
服务商通知你的服务器;你的服务器经由 Hibira 通知页面

步骤 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 也是同一个做法

小提醒

  • 尽快回应服务商 200。慢的工作放在响应之后或后台任务里做,请看长时间任务的进度。
  • 只转发页面需要的内容(状态和 ID),不要转发服务商的原始内容:里面可能有个人数据。
  • 用户已经关掉页面的话,按推送通知指南发送推送。
  • 整个团队共用的仪表盘,可以一个团队一个频道,不必每张订单一个。