Webhook
通知机制、请求头、事件结构和签名验证。
Stabo 通过 Webhook 把事件通知(如收款成功)主动推送到你的服务器。你无需轮询,即可实时获知账户变动。
所有 Webhook 请求都包含 HMAC-SHA256 签名。你必须验证签名,确认请求来自 Stabo,防止伪造攻击。详见下文签名验证。
通知机制
- 协议:HTTPS(TLS 1.2+)
- 请求方式:HTTP POST
- 请求地址:在控制台「集成 → Webhooks」配置的 Webhook URL(必须使用 HTTPS)
- 内容类型:application/json
- 签名算法:HMAC-SHA256
- 重试策略:如果响应状态码不是 2xx,Stabo 将进行指数退避重试,最多重试 5 次
- 重试间隔:1 分钟、5 分钟、30 分钟、2 小时、6 小时
- 超时时间:30 秒(建议在 30 秒内返回响应)
最佳实践:收到 Webhook 通知后,建议先返回 2xx 响应,再异步处理业务逻辑,避免处理超时导致重复推送。
请求头
Stabo 发送的每个 Webhook 请求都包含以下 HTTP Header:
| Header | 说明 | 示例 |
|---|---|---|
Content-Type | 请求内容类型 | application/json |
Webhook-Event-Id | 事件唯一 ID | evt_1234567890 |
Webhook-Event-Type | 事件类型 | checkout.payment_intent.succeeded |
Webhook-Signature | 签名信息(用于验证请求真实性) | t=1678888888,v1=a3f7c8b9... |
事件对象结构
{
"id": "evt_1Mxxx",
"object": "event",
"type": "checkout.payment_intent.succeeded",
"created": 1678888888,
"data": {
"object": {}
}
}字段说明:
id: 事件唯一标识符object: 固定值"event"type: 事件类型(如checkout.payment_intent.succeeded)created: 事件创建时间戳(秒)data.object: 事件数据载荷(根据事件类型不同而变化)
各产品的事件类型和载荷见产品指南,例如 Checkout 的 Webhook 事件。
签名验证
你必须验证每个请求的签名,确保 Webhook 请求来自 Stabo,而非第三方伪造。
Webhook-Signature 的格式为 t=timestamp,v1=signature。验证步骤:
- 解析 Header,取出
t(时间戳,秒)和v1(签名)。 - 校验时间戳,拒绝过期请求。容差由商户自行决定,下面的示例取 300 秒。
- 构造待签名字符串:
eventId + "." + timestamp + "." + rawBody。eventId取自Webhook-Event-Id,rawBody是未经解析的原始请求体。 - 用 Webhook 密钥对待签名字符串计算 HMAC-SHA256,结果转为小写十六进制字符串。
- 常数时间比较期望签名与
v1。
必须使用原始请求体计算签名。不要先解析 JSON 再重新序列化,字段顺序或空白的任何变化都会让签名不一致。密钥按字符串的 UTF-8 字节使用,不要做 Base64 解码。
验证代码示例
用 openssl 手工算出期望签名,适合联调时排查签名不一致。
# 变量取自请求: EVENT_ID=Webhook-Event-Id, TIMESTAMP=Webhook-Signature 中的 t
# RAW_BODY 是原始请求体,WEBHOOK_SECRET 是 Webhook 密钥
printf '%s' "${EVENT_ID}.${TIMESTAMP}.${RAW_BODY}" \
| openssl dgst -sha256 -hmac "${WEBHOOK_SECRET}" \
| sed 's/^.* //'输出应与 Webhook-Signature 中的 v1 相同。
用固定样本自测
下面的样本只用于自测,密钥是示例值。用上面的任一实现,传入样本,应得到 true。验证时把当前时间设为 t 前后 300 秒内。
| 项目 | 值 |
|---|---|
| 密钥 | c2VjcmV0LWZvci1kb2NzLXRlc3Qtb25seS0wMDAwMDAwMDAwMA== |
Webhook-Event-Id | evt_1234567890 |
Webhook-Signature | t=1778480710,v1=86d39077e56f6d8b745f2aaff8f52af99e4219b978cdb4269884082745d7bc63 |
原始请求体(单行,无结尾换行):
{"id":"evt_1234567890","object":"event","type":"checkout.payment_intent.succeeded","created":1778480710,"data":{"object":{"paymentIntentId":"pi_MNOL0POUV8IDPXCD","status":"COMPLETED","amount":"20.00","currency":"USD","note":"测试 UTF-8"}}}改动请求体中的任何一个字符,或把 v1 改成大写,验证都应返回 false。