---
url: /docs/billing/webhooks.md
---
# 支付事件回调

## 路径与签名

Stripe 插件提供 `POST /api/auth/stripe/webhook`，验证签名后更新 `subscription` 表。回调签名使用 `STRIPE_WEBHOOK_SECRET`，不能用普通 API 密钥替代。

配置目标网址为你的 `APP_ORIGIN` 加上该路径。插件处理的核心事件包括：

```text
checkout.session.completed
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
```

这些事件用于同步创建、状态、周期和取消信息。具体行为以锁定版本插件及真实事件验收为准，不能只以浏览器结账回跳为成功依据。

## 本地转发

```sh
stripe listen --forward-to http://localhost:4400/api/auth/stripe/webhook
```

使用 Stripe CLI 输出的签名密钥更新 `.env.local`，填齐其他支付凭据并重启接口。如果 CLI 会话、账号或转发目标变化，重新核对当前签名密钥。

## 原始请求体

签名验证需要原始请求体。入口保护可以有界读取并保留原始字节，再交给插件；不要转换或重新序列化它。反向代理也要保留回调方法、请求体与签名头。

## 排查与验收

签名失败检查测试或正式模式、密钥及请求体；404 检查支付启用条件与路径；数据库未更新检查事件投递日志和迁移。

测试事件重投、乱序到达、取消和试用变化。新增积分发放等自定义处理时使用唯一事件键，不能每收到一次通知就重复加积分。当前插件订阅接入不包含一次性积分包到账处理。

## 锁定版本的保护与缺口

回调在原始字节验签和模式核对之后，按本人 / 组织主体获取数据库租约，再用 SDK 读取当前资源。Checkout 通知还读取当前会话并核对关联。已存在订阅走插件更新，已知唯一客户的新订阅走插件创建，保持 Better Auth 对订阅表的写入职责。历史通知只触发当前状态核对，不能直接把旧载荷写回。原始请求仍由插件真实验签；没有将新对象重新编码成签名请求。

插件创建 / 更新的短事务内检查保存结果，不一致回滚；`onEvent` 在提交后继续使用 uncached 数据库核对，覆盖创建、状态、方案、席位、周期、取消与日程关联。供应商读取失败或资源列表不完整返回 503；插件内部吞掉数据库错误后，保存校验仍返回非 2xx。适配器将锁定创建处理器遗漏的取消 / 结束 / 日程字段补入同一次插件创建，不另写状态表；数据库触发器静默改动状态也会被校验并回滚。每个 SDK 客户端独立包装验签方法，避免共享对象串用模式或租约。

内部事件类型用于选择当前对象的插件处理路径，不保证原通知对应的完成 / 删除钩子触发。新邮件、积分等一次性副作用需要独立持久去重与发件箱，按 I12 实施；本轮没有新增此类副作用。当前状态读取不是 Stripe 和本地数据库的原子事务，新变化依赖后续通知收敛。GET 回跳复用当前资源和保存校验；锁定版本补丁修复已可用订阅跳过更新及结束 / 日程字段遗漏。没有伪造签名事件，回跳不作为单独付款确认。

原始载荷上限为 256 KiB，超过返回 413；租约忙返回 503，旧令牌不能写插件状态。`bun subscriptions:validate` 验证八个实际 PostgreSQL 连接、Better Auth handler 和受控 SDK HTTP，包括七个并发通知先重试、随后均保存当前状态。`bun payments:validate` 在生产 Worker 验证正常事件、旧通知、读取失败和数据库故障重投，SDK 订阅读取经本地出站拦截提供受控结果，没有访问实际 Stripe。原生 Worker 另用实际注册会话验证 GET 回跳、已可用状态更新、读取 / 保存失败无跳转及修复重试；所有客户创建、Checkout / 订阅读取均为受控 SDK HTTP。两者不能证明真实收款或原生 Checkout 创建 / 门户 HTTP 已验收。

真实沙箱、年付首购界面、未知过期 Checkout 自动找回和生产绑定继续按 I6 验收。结账原请求恢复、状态权限和其他已知缺口见[中文教程](../agentbuff-stack/billing-tasks#订阅结账保护与恢复)。
