> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook - 处理最佳实践

> Webhook 处理的最佳实践：幂等性、快速响应、安全性等。

## 使用 SDK 内置的 WebhookHandler

我们强烈建议使用 SDK 方法 `webhook().handleWebhook()`，它会自动处理：

* 签名校验
* JSON 解析与事件路由
* 响应体构建与签名

```typescript theme={null}
const handler = waffo.webhook()
  .onPayment((n) => handlePayment(n))
  .onRefund((n) => handleRefund(n))
  .onSubscriptionStatus((n) => handleSubscription(n))
  .onSubscriptionChange((n) => handleChange(n));

app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const result = await handler.handleWebhook(req.body.toString(), req.headers['x-signature'] as string);
  res.setHeader('X-SIGNATURE', result.responseSignature);
  res.status(200).send(result.responseBody);
});
```

## 幂等处理

Waffo 可能会多次投递同一个事件。请确保你的处理逻辑具备幂等性：

```typescript theme={null}
waffo.webhook().onPayment(async (notification) => {
  const orderId = notification.acquiringOrderId;

  const order = await db.order.findByAcquiringOrderId(orderId);
  if (order.status === 'PAY_SUCCESS') {
    return;
  }

  await updateOrderStatus(orderId, notification.orderStatus);
});
```

## 按最终状态处理，不依赖投递顺序

Webhook 是异步分发的通知，商户收到多个相关回调的顺序不保证。尤其是订阅首期场景中，`SUBSCRIPTION_STATUS_NOTIFICATION` 和 `SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION` 都可能发送，请不要依赖这两个回调的到达顺序。

* 不要按回调到达顺序更新最终业务状态
* 以 `subscriptionId`、事件 ID 或业务订单 ID 做幂等去重
* 如果同时订阅 `SUBSCRIPTION_STATUS_NOTIFICATION` 和 `SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION`，收到任一回调后调用 `POST /api/v1/subscription/inquiry` 查询最终状态，再进入统一的订阅回调处理逻辑

## 快速响应

* SDK 会在 handler 执行完成后自动构建响应
* 耗时操作（例如发送邮件或更新外部系统）应异步处理
* 如果在 handler 中抛出异常，SDK 会自动返回失败响应

## 安全

* 始终校验 `X-SIGNATURE`（由 SDK 自动处理）
* 使用 HTTPS 端点
* **在处理事件前先校验签名**——在校验通过之前不要执行任何业务逻辑
* 响应必须包含 `X-SIGNATURE` header（由 SDK 自动处理）
