API 参考:webhook
事件一发生,webhook 就把它推到您的服务器。在更多 → 商户 API里给您的应用设好 Webhook URL;此后您订阅的每一个事件,平台都会 POST 一段带签名的 JSON。
外层结构
{
"update_id": 123,
"update_type": "invoice_paid",
"request_date": "2026-08-11T12:00:00Z",
"payload": { … }
}
同一个事件重复投递时,update_id 保持不变——请拿它做去重。request_date 是每次投递尝试
各自打的。payload 是该事件类型的完整对象:账单类事件给账单对象,红包类给红包对象,
订阅类给订阅对象(各自的结构见对应的参考页面)。
事件类型
update_type | 什么时候触发 | Payload |
|---|---|---|
invoice_paid | 账单被付掉——一定会投递 | 账单对象 |
invoice_expired | 账单到了期限还没付 | 账单对象 |
check_activated | 您的某个红包被领了 | 红包对象 |
refund_completed | 一笔退款执行完成 | 带 refunded_* 字段的账单对象 |
subscription_activated | 用户完成了套餐授权 | 订阅对象 |
subscription_charged | 某一期扣款完成——charge.kind 说明是哪一种:initial、renewal 或 resubscribe | 订阅对象加 charge:{period_no, kind, asset, amount, fee, paid_at} |
subscription_cancelled | 订阅被任意一方取消 | 订阅对象 |
subscription_expired | 宽限期过完仍未付款 | 订阅对象 |
除 invoice_paid 之外的一切都要主动订阅(这是相对 Crypto Bot 的扩展——严格照 Crypto Bot
写的消费端,除非您自己去开,否则永远碰不到一个陌生的 update_type)。请在商户 API 页面上
按应用去额外的 webhook 事件里开——这些开关在设好 webhook URL 之后出现,标签就是上表里
那些原样的标识符。
怎么验签
每次投递都带着 TgPayCrypto-API-Signature 请求头(Crypto-Pay-API-Signature 是取值
相同的兼容别名):对原始请求体做十六进制 HMAC-SHA256,密钥是您应用主 API 令牌的
SHA-256 摘要。这就是 Crypto Bot 那套方案,所以现成的验签代码原样就能用:
import hashlib, hmac
secret = hashlib.sha256(API_TOKEN.encode()).digest()
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, headers["TgPayCrypto-API-Signature"])
请对收到的原始字节验签——重新序列化一遍的结果可能逐字节地不一样,验签就过不了。只有主 令牌签名;受限令牌永远不签。更换主令牌会立刻给 webhook 签名换密钥,所以请在同一次操作里 把服务器上的密钥也更新掉。
投递和重试
- 10 秒内返回任意 2xx 就算投递成功。
- 其余情况——错误状态码、超时、连接失败——都会以指数退避重试:第一次重试大约在 10 秒之后, 间隔翻倍直到 8 小时,最多 17 次,摊在大约 3 天里。
- 最后一次尝试之后,这次投递就丢弃了。webhook URL 本身永远不会被自动停用——一个不稳的接收端 不会悄悄把您的应用退订掉。
- 正因为有重试,您的处理逻辑必须是幂等的:处理之前先按
update_id去重。
⚠️ 先验签,再发货
任何人都能往您的 webhook URL 上 POST。在签名校验通过之前,请把请求体当作不可信输入:别发货、
别给用户入账、别把什么标记成已付款。安全的顺序是:验签 → 按 update_id 去重 → 处理。
这篇文章帮上忙了吗?
谢谢反馈。