集成 Webhook
要在应用程序中接收 webhook 事件,请按照以下步骤创建并注册 webhook 端点::
- 创建 webhook 端点处理器,用于接收事件数据的 POST 请求。
- 使用仪表板 注册你的端点。
- 保护你的 webhook 端点。
- 使用测试付款 测试你的 webhook 端点。
1. 创建处理器
设置一个能够通过 POST 方法接受 webhook 请求的 HTTPS 端点功能。你可以使用 Webhook.site 来查看有效负载和测试传送。
确保你的端点功能:
• 处理带有 JSON 负载的 POST 请求,该负载包含一个 event 对象。 • 在执行可能导致超时的复杂逻辑之前,快速返回成功的状态码(2xx)。
2. 注册你的端点
在仪表板上注册你的 webhook 端点。
- 前往你的
主页。 - 点击
创建新 webhook。 - 填写你的 URL 和可选描述,并选择你希望接收的事件。我们建议至少选择
order.succeeded以确保不遗漏成功的客户订单。 - 点击
创建 webhook。 - 复制并保存返回的签名密钥。你需要它来验证收到的 webhook。出于安全原因,我们只会显示一次密钥。

3. 保护你的 webhook 端点
Tokenz 所发送的所有 webhook 都包含签名,你必须验证这个签名以确保 webhook 来自 Tokenz 且未被篡改。或者,你可以将 webhook 通知仅作为触发器,通过 API 检查订单状态变更。不过,我们建议验证 webhook 签名,以减少不必要的 API 调用。
每个事件的签名包含在 Tokenz-Signature 标头中,包含时间戳 (t:<unix-timestamp-in-milliseconds>) 和一个或多个版本签名 (v1:<HMAC signature>) 需要验证。目前有效的签名版本是 v1。
Example:
t:1725864981111,v1:zjdrra3...Mh+e6s=
验证签名
1. 提取时间戳和签名
将标头按逗号(,)分隔,创建元素列表。再使用冒号(:)分隔每个元素,得到前缀和值的对。前缀 t 代表时间戳,v1 为签名。忽略其他元素。
2. 准备用于签名的有效负载
将时间戳(字符串形式)与请求主体(即 JSON 负载)的原始文本连接,组成有效负载字符串。
3. 计算签名
使用 SHA256 哈希函数计算 HMAC,端点的签名密钥作为密钥,有效负载字符串作为消息。
4. 比较签名
将标头中的签名与计算得到的签名进行比对。评估当前时间与接收到的时间戳的差异,防止重放攻击。如果签名有效但时间戳已过期,应用程序可以拒绝该请求。推荐的时间容差为 300 秒(5 分钟)。
重放攻击是指攻击者截获并重传合法有效负载及其签名。Tokenz 通过在 Tokenz-Signature 标头中包含时间戳来减少这种风险。
const crypto = require('crypto');
const express = require('express');
const app = express();
const port = 3000;
// Use express.raw() so the signature is computed over the exact bytes
// Tokenz sent; re-serializing a parsed body can fail verification.
// If your app registers express.json() or another body parser globally,
// mount this webhook route BEFORE it so the raw body is preserved.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
// The signing secret shown once on webhook creation is Base64-encoded.
const secret = 'webhook-endpoint-secret';
const sig = req.headers['tokenz-signature'];
let timestamp, signature;
for (let sigPart of sig.split(',')) {
let [key, value] = sigPart.split(':');
if (key === 't') {
timestamp = value;
} else if (key === 'v1') {
signature = value;
}
}
const checkSignature = crypto.createHmac('sha256', Buffer.from(secret, 'base64'))
.update(`${timestamp}${req.body.toString('utf8')}`)
.digest('base64');
// The `t` value in Tokenz-Signature is a Unix timestamp in milliseconds.
const tolerance = 5 * 60 * 1000; // 5 minutes
if (
signature === checkSignature &&
Math.abs(Date.now() - Number(timestamp)) <= tolerance
) {
return res.status(200).end();
} else {
return res.status(400).end();
}
});
4. 测试你的 webhook 端点
为了确保你的 webhook 处理正确,请按照我们的测试指南创建并完成一些测试付款。