回调签名与验签规范
更新时间: 2026/08/27 10:13:38
本文介绍业务方接收云信大模型 API(简称 AIHub)网关异步回调时的协议规范。
若底层模型供应商也使用回调签名,AIHub 会在网关侧完成供应商回调处理。业务方仅需按照本文校验 AIHub 发往 callback_url 的回调请求即可。
技术原理
业务方在创建异步任务时可以传入 callback_url。任务状态发生变化后,AIHub 向该地址发送回调通知,并使用创建任务时的访问令牌作为签名密钥,对请求方法、请求路径、时间戳、随机数和原始请求体摘要生成 HMAC-SHA256 签名。
业务方收到回调后,应使用同一访问令牌重新计算签名,并与请求头中的签名值进行常量时间比较。验签通过后,再处理回调体中的任务状态和生成物信息。
签名机制用于确认回调来源、发现请求体篡改,并配合时间戳和随机数降低重放风险。
回调请求规范
触发条件
- 仅异步任务接口会触发业务方回调。
- 创建任务时必须传入
callback_url,AIHub 才会发起回调。 - 常见任务状态包括
processing、success、failed,具体字段以对应接口文档为准。
callback_url要求
| 规则 | 说明 | 失败错误 |
|---|---|---|
| 长度 | 不超过 512 个字符 | callback_url_too_long |
| URL 结构 | 必须包含 scheme 和 host | callback_url_invalid |
| 用户信息 | 不允许包含 userinfo | callback_url_invalid |
| 协议 | 生产环境推荐并默认要求 HTTPS | callback_url_scheme_forbidden |
| 裸 IP | 不得命中私网、回环、链路本地等地址 | callback_url_private_ip |
方法
httpPOST {user_callback_url}
Content-Type: application/json; charset=utf-8
业务方需返回 2xx 状态码表示接收成功。AIHub 不会跟随 3xx 跳转;如需迁移回调地址,请在创建任务时传入最终地址。
请求头
| Header | 示例值 | 说明 |
|---|---|---|
Content-Type |
application/json |
回调请求体格式 |
X-AIHub-Timestamp |
1776483051 |
Unix 秒级时间戳(UTC) |
X-AIHub-Nonce |
6cbf797d065943dff4eeaf18349e7934 |
随机字符串,用于辅助防重放 |
X-AIHub-Signature |
<64 位 hex> |
HMAC-SHA256 签名值(小写 hex) |
请求体
回调体为 JSON,包含当前任务状态信息。业务方可读取以下字段:
| 字段 | 说明 |
|---|---|
task_id |
任务 ID |
state |
任务状态(如 processing、success、failed) |
err_code |
错误码(失败时返回) |
credits |
积分消耗,固定为 8 位小数字符串 |
creations |
生成物列表 |
示例:
json{
"task_id": "tsk_1234567890",
"state": "success",
"err_code": "",
"credits": "50.00000000",
"creations": [
{
"id": "creation_xxx",
"url": "https://example.com/file.mp4"
}
]
}
不同任务类型的生成物字段以对应接口文档和任务查询结果为准。验签时必须使用原始请求体字节计算摘要,不要先解析 JSON 后再重新序列化。
签名算法
secret
回调验签使用创建任务时的 AIHub 访问令牌明文作为 secret:
textsecret = sk-xxxxx
string_to_sign
textHTTP_METHOD + "\n" +
REQUEST_URI + "\n" +
X-AIHub-Timestamp + "\n" +
X-AIHub-Nonce + "\n" +
sha256_hex(body)
| 组件 | 说明 |
|---|---|
HTTP_METHOD |
固定为 POST |
REQUEST_URI |
业务方回调地址的 path + raw query(不含 scheme 和 host) |
X-AIHub-Timestamp |
请求头中的时间戳(Unix 秒字符串,不要重新格式化) |
X-AIHub-Nonce |
请求头中的随机字符串 |
sha256_hex(body) |
原始请求体字节的 SHA-256 十六进制(小写) |
REQUEST_URI 需保留原始 path 编码、query 顺序和 query 编码方式,不要 URL decode 后再拼接,也不要重新排序 query 参数。
signature
textsignature = hex(HMAC-SHA256(secret, string_to_sign))
最终签名值放入请求头 X-AIHub-Signature。
验签步骤
- 读取原始 body 字节,不要预先解析或改写 JSON。
- 读取请求头:
X-AIHub-Timestamp、X-AIHub-Nonce、X-AIHub-Signature。 - 使用创建任务时的
sk-...明文作为secret。 - 按签名算法重新计算签名。
- 与
X-AIHub-Signature进行常量时间比对。
建议附加校验:
X-AIHub-Timestamp与本地时钟偏差在可接受窗口内(如 ±5 分钟)。X-AIHub-Nonce做重放保护(同一nonce在一定窗口内仅接受一次)。task_id应作为业务侧幂等键的一部分,避免重复回调导致重复处理。
Python 验签示例
pythonimport hashlib
import hmac
import time
from urllib.parse import urlparse
def verify_callback(
secret: str,
callback_url: str,
method: str,
timestamp: str,
nonce: str,
body: bytes,
provided_sig: str,
skew_seconds: int = 300,
) -> bool:
# 1. 时间窗校验(强烈建议)
try:
ts = int(timestamp)
except ValueError:
return False
if abs(int(time.time()) - ts) > skew_seconds:
return False
# 2. 构造 string_to_sign
parsed = urlparse(callback_url)
request_uri = parsed.path or "/"
if parsed.query:
request_uri += "?" + parsed.query
body_sha = hashlib.sha256(body).hexdigest()
string_to_sign = "\n".join([
method.upper(),
request_uri,
timestamp,
nonce,
body_sha,
])
# 3. 计算签名并进行常量时间比较
expected = hmac.new(
secret.encode("utf-8"),
string_to_sign.encode("utf-8"),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, provided_sig)
重试规则
默认重试退避策略:
text[0s, 10s, 60s]
处理规则:
- 业务方返回任意
2xx:视为成功,不再重试 - 业务方返回任意
4xx:视为最终失败,不再重试 - 业务方返回
3xx、5xx或发生网络错误、超时:按退避策略继续重试 - 重试耗尽后仍未成功:本次回调投递结束,业务方应通过任务查询接口兜底查询最终状态
重试耗尽后仍未成功,本次回调投递结束。业务方应通过任务查询接口兜底获取最终状态:
httpGET /hub/vidu/v2/tasks/{task_id}/creations
业务方处理回调时应保证幂等。对于同一个 task_id,以最新任务状态和生成物为准;若同时使用回调和主动查询,建议按任务终态做一次最终收敛。




