什么是webhook?React+Express开发者的Stripe支付场景疑问
理解Stripe Webhook的作用与实现逻辑
先直接说设置https://example.com/payment的作用
当你在Stripe后台把这个URL设为webhook地址后,Stripe会在发生支付相关的关键事件时,主动给这个URL发送POST请求,把事件详情推送给你的Express服务器。
比如用户付款成功、付款失败、发起退款、订阅续费成功/失败这些事件,Stripe不会等你去主动查询,而是直接把消息送到你的服务器上,让你能实时处理这些事件对应的业务逻辑。
为什么需要Webhook?
你前端和Stripe的交互只是用户操作层面的(比如用户点击付款、输入卡号),但很多核心流程是在Stripe后台完成的:
- 用户付款后,Stripe需要和银行确认资金到账,这个过程可能有延迟
- 订阅类产品到期后,Stripe会自动尝试扣费,这个事件你的前端完全感知不到
- 有些退款是用户在Stripe后台发起的,你的系统没法主动获知
没有Webhook的话,你只能靠定时去调用Stripe API查状态,不仅效率低,还可能错过实时处理的时机(比如用户付款成功后立刻要给用户发确认邮件)。
具体的工作流程(以你的URL为例)
- 配置阶段:你在Stripe后台填写
https://example.com/payment,同时获取Stripe提供的webhook签名密钥(用来验证请求的合法性) - 事件触发:当Stripe检测到某个事件(比如
charge.succeeded,即支付成功),它会把这个事件的JSON数据打包成POST请求,发送到你的/payment路由 - 服务器处理:你的Express服务器在
/payment路由上编写处理逻辑:- 先验证这个请求确实来自Stripe(防止有人伪造请求搞破坏)
- 根据事件类型执行对应的业务操作:比如更新数据库里的订单状态、发送邮件通知用户、解锁用户购买的服务
- 响应Stripe:处理完后给Stripe返回200状态码,告诉它你已经收到事件了;如果Stripe没收到200,会重试发送请求
新手友好的Express代码示例
// 先确保安装stripe包:npm install stripe const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const express = require('express'); const app = express(); // 注意:要保留原始请求体,因为Stripe的签名是基于原始body生成的 // 配置Express保留raw body app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } })); // 处理webhook的路由 app.post('/payment', async (req, res) => { // 第一步:验证请求来自Stripe const sig = req.headers['stripe-signature']; const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; let event; try { event = stripe.webhooks.constructEvent(req.rawBody, sig, webhookSecret); } catch (err) { // 验证失败,直接返回400 return res.status(400).send(`Webhook Error: ${err.message}`); } // 第二步:根据事件类型处理业务逻辑 switch (event.type) { case 'charge.succeeded': const successfulCharge = event.data.object; // 这里写你的逻辑:比如更新订单状态、发确认邮件 console.log(`Payment succeeded for order: ${successfulCharge.metadata.orderId}`); break; case 'charge.failed': const failedCharge = event.data.object; // 处理付款失败:通知用户、更新订单为失败状态 console.log(`Payment failed for customer: ${failedCharge.customer}`); break; // 其他常用事件:invoice.paid(订阅续费成功)、charge.refunded(退款完成)等 default: console.log(`Unhandled event type: ${event.type}`); } // 告诉Stripe我们收到了事件 res.json({ received: true }); }); app.listen(3000, () => console.log('Server running on port 3000'));
新手容易踩的坑
- 必须验证签名:这一步绝对不能省,不然恶意攻击者可以随便发个POST请求到你的
/payment,伪造支付成功的事件,导致你的系统给用户解锁服务但实际没收到钱 - 本地开发测试:Stripe没法访问你的localhost,所以需要用ngrok这类工具生成一个公网临时域名,映射到你的本地端口(比如
https://xxxx.ngrok.io/payment),然后填到Stripe后台测试 - 处理重试逻辑:如果你的服务器没返回200,Stripe会重试发送事件,所以你的处理逻辑要做成幂等的(比如同一个事件处理多次也不会重复执行操作,比如不会重复发邮件)
内容的提问来源于stack exchange,提问作者Safwan Parkar
相关产品推荐
相关产品推荐

