PAYATHON 2026

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 后,按以下流程处理:

  1. 验证 X-ANET-Signature,确认请求来自 Authorize.Net。
  2. 从 Webhook 的 payload.id 中取得交易 ID(transId)。
  3. 调用 getTransactionDetails 查询完整的交易信息。
  4. 检查交易中的订阅关联信息,例如 subscription.id。
  5. 使用 transId 做幂等处理,避免 Webhook 重试造成重复入账。
  6. 更新本地订单、账单或会员有效期。

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 地址、凭据与签名密钥。上线前还要使用真实的事件通知验证签名、幂等和异常处理流程。

备注:内容仅供参考。