在您的 Webhook 端点接收 Tokenz 事件
在您的 Webhook (网络钩子) 端点监听 Tokenz 帐户中的事件,以便您的系统集成能够自动触发响应操作。
金额格式: Webhook 负载中的所有货币值(如 amount 字段)均以各货币的最小单位表示。请参阅支持的货币了解每种货币的确切编码。
为什么使用 Webhook
在构建 Tokenz Checkout (结账) 集成时,您的应用程序应该能够实时接收帐户中的事件,以便您的后端系统能够执行相应的操作。
要启用 Webhook 事件,您需要注册 Webhook 端点。注册完成后,Tokenz 会在事件发生时,将实时的事件数据推送到您的应用程序的 Webhook 端点。Tokenz 通过 HTTPS 将事件以包含 Event 对象的 JSON 格式发送给您的应用程序。
我们强烈建议除了处理 Checkout 重定向外,还实现 Webhook,以确保您始终能收到 Checkout 结果的通知。
Event 对象
当事件发生时,Tokenz 会生成一个新的 Event 对象。一次 API 请求可能会生成多个事件。
通过在 Tokenz 账户中注册 Webhook 端点,您可以让 Tokenz 自动将 Event 对象作为 POST 请求的一部分发送到您应用程序托管的 Webhook 端点。当您的 Webhook 端点收到 Event 后,您的应用程序可以执行后端操作(例如,在收到 order.succeeded 事件后,向客户发送购买商品的下载链接或为他们的账户充值)。
我们发送到您 Webhook 端点的 Event 对象提供了已更改对象的快照。Webhook 始终包含发生事件的完整对象及其事件元数据。
事件 payload 示例
以下事件显示了一个成功订单的事件。
{
"id": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6",
"object": "order.succeeded",
"createdAt": "2025-09-24T03:22:20.297Z",
"test": true,
"eventData": {
"type": "order",
"version": "v2",
"data": {
"order": {
"id": "order_1D5xA9BnKfv_t",
"object": "order",
"status": "succeeded",
"amount": {
"amount": 3700,
"currency": "JPY"
},
"items": [
{
"id": "item_1D6F8mR5TnK_t",
"detail": {
"product": {
"price": {
"amount": 1200,
"currency": "JPY"
},
"quantity": 3,
"label": "ひとにぎりのエメラルド",
"description": "80+8",
"images": [
"https://images.ctfassets.net/z82qbo7cv7ia/1dWPbk5Qx2M1Qikj6Knyuc/e37b2c26829c0d30793a348ae3adb3b0/fake-pass.webp"
]
}
}
},
{
"id": "item_1xejV7puGeK_t",
"detail": {
"product": {
"price": {
"amount": 100,
"currency": "JPY"
},
"quantity": 1,
"label": "Diamond",
"description": "",
"images": [],
"sku": "sku_103843dfjhdfgiu",
"taxCategory": "VIRTUAL_CURRENCY"
}
}
}
],
"description": "Game items purchase",
"reference": "ORDER_2024_001",
"test": true,
"expiresAt": "2024-12-31T23:59:59Z",
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:05:30Z"
}
}
}
}
正式和测试模式
您可能会接收到来自正式和测试模式的事件传送请求,这取决于您是否使用单个端点来处理这两种模式。使用测试标志来检查对象是存在于正式还是测试模式,并确定事件的正确处理方式。
版本
version 表示事件的 API 版本并决定包含的 data.object 的结构。
事件的种类
| 种类 | 说明 |
|---|---|
order.created | 当通过 Create a Checkout Session API 创建一个新的 order 对象时触发。 |
order.succeeded | 当消费者成功支付一笔 order 时触发,这时您可以向客户提供产品。 |
order.expired | 当订单过期时触发。 |
order.canceled | 当订单被取消时触发。 |
order.failed | 当订单支付失败时触发。 |
refund.created | 当退款被创建时触发。 |
refund.failed | 当退款失败时触发。 |
dispute.created | 当争议被创建时触发。 |
dispute.closed | 当争议被关闭时触发。 |
redemption.completed | 礼品码兑换或免费道具领取完成。使用 kind 区分流程(source 已弃用);免费道具领取不包含 code。 |
重试与幂等性
Tokenz 期望您的端点以 2xx 响应确认收到 Webhook。如果您的端点返回其他状态码、超时或无法访问,Tokenz 会在最多三天内自动重试投递。首次重试大约会在投递失败后的 5 秒、15 秒和 30 秒进行。之后重试间隔会逐渐增加,频率也会降低。重试时间可能略有变化;请勿依赖后续重试的精确时间表。测试模式的 Webhook 投递可能采用不同的重试策略,因此请勿用它来衡量或验证正式模式的重试时间。一旦您的端点返回 2xx,Tokenz 将停止重试该事件。您的端点可能会多次收到同一事件,且事件到达顺序不受保证。
请以幂等方式处理投递:
- 在安全接收事件后尽快返回 2xx,将耗时的处理放到异步执行。
- 使用事件顶层的
id作为幂等键: 记录已处理的事件 ID 并跳过已处理过的事件,避免重试导致订单被重复履行。
故障排查:我的端点没有收到事件
绝大多数情况下,“Webhook 从未送达”其实意味着 Tokenz 确实发送了它,而你的端点拒绝了它。Tokenz 会将任何非 2xx 的响应(或超时、无法访问的端点)视为投递失败,并在最多三天内自动重试——因此在你这一侧看起来像是什么都没收到,而 Tokenz 却一直看到同一次投递不断失败(例如连续出现一串 HTTP 400)。
由于 Tokenz 不提供按端点划分的投递日志,请从你自己的端点进行诊断:
1. 检查你的端点返回的 HTTP 状态码。 这正是 Tokenz 所看到的——请在服务器的访问日志/错误日志中查找收到的 POST 请求。
2xx— 事件已投递并被确认。如果你的系统仍然没有据此执行操作,那么问题出在响应之后的处理器中,而不是投递环节。400、401、403— 你的端点收到了事件但拒绝了它(见下文)。5xx或超时 — 你的处理器出错,或响应太慢。
2. 如果你返回的是 400,请先检查签名验证——这是最常见的原因:
- 对原始请求体进行验证。 大多数框架会解析并重新序列化 JSON,这会改变字节内容并破坏 HMAC。请读取确切的原始字节(例如使用
express.raw()),先验证,然后再解析。参见保护你的 webhook 端点。 - 使用正确的签名密钥。 密钥只在你注册端点时显示一次,并且是该端点专用的;被轮换或输入错误的密钥会导致每个事件都验证失败。
- 匹配测试模式和正式模式。 用正式模式的签名密钥去验证测试模式的事件(或反过来)永远不会匹配。如果一个端点同时处理两种模式,请根据事件的
test标志来选择密钥。 - 注意时钟偏差。 如果你按 5 分钟的时间戳容差进行拒绝,服务器时钟严重偏差可能会导致原本有效的事件被拒绝。
3. 导致非 2xx 响应的其他原因:
- 端点前面的防火墙、WAF 或 IP 允许列表拦截了 Tokenz 的请求。
- 端点无法通过 HTTPS 公开访问,或在仪表板中注册的 URL 有误或指向了错误的环境。
- 在请求处理路径中执行了繁重的工作导致超时——请先用
2xx确认收到,然后再异步处理。
4. 独立确认投递。 将你的端点(或其副本)指向 Webhook.site,以确认 Tokenz 正在投递,并检查确切的有效负载和标头,然后用测试模式付款触发一个事件。
一旦你的端点返回 2xx,重试就会停止。由于重试可能会重新发送你已经处理过的事件,请保持你的处理器幂等(见上文)。