外观
Webhook 回调
创建任务时传入 webhook_url,任务结束后我们会向该地址发送 POST 请求。
请求内容
请求头:
http
Content-Type: application/json
X-Webhook-Signature: 5f2b8c...请求体:
json
{
"timestamp": 1789632021,
"task": {
"id": "task_3kP9xQ2mZ7aB1cD4eF6gH8jK",
"idempotency_key": "order_10086",
"status": "succeeded",
"output": { "images": ["https://cdn.example.com/outputs/abc.png"] },
"error": null
}
}| 字段 | 说明 |
|---|---|
timestamp | 发送时间(Unix 秒),每次发送都会更新 |
task | 任务对象,与查询任务的返回相同。通过 idempotency_key 对应你的业务记录,status 为 succeeded 或 failed |
响应与重试
- 返回
2xx即表示接收成功,请在 10 秒内响应。 - 否则会在 1 分钟、5 分钟、30 分钟、2 小时、6 小时后重试,共发送 6 次。
- 同一个任务可能收到多次推送,请用
task.id去重。
校验签名
签名密钥在控制台「Webhook」页面获取。
- 计算
HMAC-SHA256(密钥, 原始请求体),与请求头X-Webhook-Signature比较,一致才处理。 - 检查
timestamp与当前时间相差不超过 5 分钟。
注意:必须使用收到的原始请求体计算,不要先解析 JSON 再序列化。
js
import crypto from 'node:crypto'
function verify(rawBody, signature, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
if (expected.length !== signature?.length
|| !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return false
}
const { timestamp } = JSON.parse(rawBody)
return Math.abs(Date.now() / 1000 - timestamp) < 300
}python
import hashlib
import hmac
import json
import time
def verify(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature or ""):
return False
return abs(time.time() - json.loads(raw_body)["timestamp"]) < 300