Authorize.Net Recurring Billing 应监听哪个 Webhook 事件?
支付阿杰
明确结论
月度订阅续费时,重点监听以下事件:
net.authorize.payment.authcapture.created
Authorize.Net 的 ARB(Automated Recurring Billing)每次扣款都会生成一笔新的支付交易。net.authorize.customer.subscription.created 表示订阅记录已创建,net.authorize.customer.subscription.updated 表示订阅配置发生了变化,两者都不能用来判断续费是否成功。
推荐处理流程
收到 net.authorize.payment.authcapture.created 后,按以下流程处理:
- 验证
X-ANET-Signature,确认请求来自 Authorize.Net。 - 从 Webhook 的
payload.id中取得交易 ID(transId)。 - 调用
getTransactionDetails查询完整的交易信息。 - 检查交易中的订阅关联信息,例如
subscription.id。 - 使用
transId做幂等处理,避免 Webhook 重试造成重复入账。 - 更新本地订单、账单或会员有效期。
Webhook 通知通常只有事件类型、交易 ID 等少量信息。收到通知后,不能直接认定款项属于某个用户,还需要通过交易详情确认金额、状态、订阅 ID 和客户信息。
Webhook 示例
成功的周期性扣款通知大致如下,具体字段以实际收到的通知为准:
{
"notificationId": "notification-id",
"eventType": "net.authorize.payment.authcapture.created",
"eventDate": "2026-08-29T10:00:00Z",
"webhookId": "webhook-id",
"payload": {
"responseCode": 1,
"authCode": "ABC123",
"avsResponse": "Y",
"authAmount": 29.99,
"entityName": "transaction",
"id": "1234567890"
}
}
其中,payload.id 是交易 ID,不是 subscription ID。要取得订阅关联信息,还需要调用交易详情接口。
Node.js 处理示例
下面省略 SDK 初始化和签名验证的具体实现,只展示事件分流与交易查询逻辑:
app.post(
"/webhooks/authorize-net",
express.raw({ type: "application/json" }),
async (req, res) => {
const rawBody = req.body;
const signature = req.get("X-ANET-Signature");
if (!verifyAuthorizeNetSignature(rawBody, signature)) {
return res.sendStatus(401);
}
const event = JSON.parse(rawBody.toString("utf8"));
// 尽快确认接收,生产环境通常应将后续处理放入任务队列。
res.sendStatus(200);
if (event.eventType !== "net.authorize.payment.authcapture.created") {
return;
}
const transId = String(event.payload.id);
// transId 应在数据库中设置唯一约束,保证幂等。
if (await transactionAlreadyProcessed(transId)) {
return;
}
const transaction = await getTransactionDetails(transId);
const subscriptionId = transaction.subscription?.id;
if (!subscriptionId) {
// 此事件也可能来自普通的一次性 authCapture 支付。
return;
}
await recordRecurringPayment({
transId,
subscriptionId: String(subscriptionId),
amount: transaction.authAmount,
status: transaction.transactionStatus
});
}
);
getTransactionDetails 请求的核心参数如下:
{
"getTransactionDetailsRequest": {
"merchantAuthentication": {
"name": "API_LOGIN_ID",
"transactionKey": "TRANSACTION_KEY"
},
"transId": "1234567890"
}
}
不要使用 subscription.updated 判断续费
这些事件分别用于不同场景:
net.authorize.customer.subscription.created:订阅创建。net.authorize.customer.subscription.updated:订阅金额、付款资料或计划等配置发生更新。net.authorize.customer.subscription.cancelled:订阅被取消。net.authorize.customer.subscription.suspended:订阅被暂停,通常需要检查连续扣款失败等原因。net.authorize.customer.subscription.terminated:订阅被终止。net.authorize.payment.authcapture.created:成功创建一笔授权并捕获的支付交易,包括 ARB 周期扣款产生的交易。
实际业务中,通常需要同时监听支付事件和订阅状态事件:
net.authorize.payment.authcapture.created
net.authorize.customer.subscription.suspended
net.authorize.customer.subscription.terminated
net.authorize.customer.subscription.cancelled
注意事项
net.authorize.payment.authcapture.created不只会由订阅触发,普通的authCaptureTransaction也可能产生该事件。必须根据getTransactionDetails返回的订阅信息进行区分。- 不要把
subscription.updated当作“续费成功”的依据,因为它不等同于资金交易。 - Webhook 可能重复投递或延迟到达。应使用
transId保证幂等,并允许事件重试。 - HTTP
200只表示通知已被成功接收。耗时操作适合异步执行,以免响应超时造成重复投递。 - 续费失败时不会产生成功支付事件。应结合订阅的
suspended、terminated等状态事件,并根据需要主动查询订阅或交易状态。 - Sandbox 和生产环境应分别配置 Webhook 地址、凭据与签名密钥。上线前还要使用真实的事件通知验证签名、幂等和异常处理流程。
备注:内容仅供参考。
