ส่ง webhook จากบริการภายนอกไปยังหน้าที่ผู้ใช้เปิดอยู่
ชำระเงินสำเร็จ พัสดุเคลื่อนที่ บิลด์เสร็จ: ส่งต่อ webhook จาก Stripe บริษัทขนส่ง หรือ GitHub ไปยังหน้าที่ผู้ใช้กำลังดู โดยไม่ต้องรีโหลด
ลูกค้าชำระเงินบน Stripe Checkout แล้วกลับมาที่หน้า "ขอบคุณ" ของคุณ แต่การยืนยันการชำระเงินมาทาง webhook ซึ่งอาจมาช้ากว่าไม่กี่วินาที หน้าเว็บจึงแสดง "กำลังยืนยันการชำระเงิน…" ลูกค้าก็รีโหลด หรือแย่กว่านั้นคือจ่ายซ้ำ บริษัทขนส่ง บริการ CI เครื่องมือเซ็นเอกสาร และมาร์เก็ตเพลสก็ทำงานแบบเดียวกัน ข่าวไปถึงเซิร์ฟเวอร์ของคุณ ไม่ใช่หน้าที่ผู้ใช้กำลังดู เมื่อใช้ Hibira ตัวจัดการ webhook จะส่งต่ออัปเดตไปยังหน้านั้นทันทีที่ได้รับ
ผู้ใช้ของคุณได้อะไร
- เห็น "ได้รับการชำระเงินแล้ว" บนหน้าที่กำลังดูอยู่ โดยไม่ต้องรีโหลด
- อัปเดตการขนส่ง การ build และการเซ็นเอกสารแสดงขึ้นเอง
- ไม่มีการจ่ายซ้ำเพราะคิดว่าครั้งแรกล้มเหลว
สร้างด้วย
- ช่องส่วนตัว (
private-order-42) ที่เฉพาะเจ้าของคำสั่งซื้อเข้าได้ ตรวจสอบโดยปลายทางอนุญาตของคุณ triggerจากตัวจัดการ webhook เดียวกันที่ตรวจลายเซ็นของผู้ให้บริการและบันทึกการเปลี่ยนแปลง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 | build หรือ deploy เสร็จ | ป้ายสถานะและลิงก์พรีวิว |
| เครื่องมือเซ็นเอกสาร | เซ็นเอกสารแล้ว | "ทั้งสองฝ่ายเซ็นแล้ว" |
| Shopify, มาร์เก็ตเพลส | สร้างหรือชำระคำสั่งซื้อ | แถวใหม่บนแดชบอร์ดผู้ขาย |
เคล็ดลับ
- ตอบผู้ให้บริการด้วย 200 ให้เร็ว งานที่ช้าให้ทำหลังตอบหรือในงานเบื้องหลัง ดูที่ความคืบหน้าของงานที่ใช้เวลานาน
- ส่งต่อเฉพาะสิ่งที่หน้าเว็บต้องใช้ (สถานะและ ID) อย่าส่งข้อมูลดิบของผู้ให้บริการ เพราะอาจมีข้อมูลส่วนบุคคล
- ถ้าผู้ใช้ปิดหน้าไปแล้ว ให้ส่งพุชตามคู่มือการแจ้งเตือนแบบพุช
- แดชบอร์ดที่ทั้งทีมใช้ร่วมกัน ใช้หนึ่งช่องต่อทีมแทนหนึ่งช่องต่อคำสั่งซื้อได้