外部サービスの 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、マーケットプレイス | 注文作成・支払い | 販売者ダッシュボードに新しい行 |