API v1

验证回调签名

用登记地址时拿到的签名密钥,验证每个通知确实来自我们。

查看 Markdown
本页内容

每个通知都带 Chenvoya-Signature 请求头。先验证签名,再读取正文;验证失败时返回非 2xx 状态并丢弃这次请求。

签名格式

Chenvoya-Signature: t=1793167400,v1=<64 位小写十六进制>

t 是签名时间(Unix 秒),v1 是小写十六进制的 HMAC-SHA256。

验证步骤

  1. 把登记时取得的 signing_secret 做 base64url 解码,得到 32 字节的验签密钥。
  2. 从 Chenvoya-Signature 拆出 t 和 v1。
  3. t 与本地时间相差超过 5 分钟时拒绝。
  4. 用验签密钥计算 HMAC-SHA256,输入是 chenvoya.v1.、t、一个点 .,再接正文的原始字节。
  5. 把结果写成小写十六进制,与 v1 做常量时间比较。
  6. 通过后按正文的 event_id 去重;created_at 早于 7 天零 5 分钟前或晚于现在 5 分钟以上的事件也要拒绝。

示例代码

import base64
import hashlib
import hmac
import time

def verify(signing_secret, header, body):
    fields = {}
    for item in header.split(","):
        name, _, value = item.strip().partition("=")
        fields[name] = value
    t, v1 = fields.get("t", ""), fields.get("v1", "")
    if not t.isdigit() or abs(time.time() - int(t)) > 300:
        return False
    key = base64.urlsafe_b64decode(signing_secret + "=" * (-len(signing_secret) % 4))
    signed = b"chenvoya.v1." + t.encode() + b"." + body
    expected = hmac.new(key, signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

检查清单

  • 用收到的原始字节计算签名,不要先解析 JSON 再重新编码。
  • 用常量时间比较,不要用普通的字符串相等。
  • 服务器时钟要准确,否则 5 分钟的时间窗口会误拒。
  • 按 event_id 去重,处理过的事件 ID 至少保存 7 天零 5 分钟。
  • 签名密钥和 API 密钥一样只放在服务器上。

下一步