Webhook 完成通知
Seedance 任务进入成功或失败终态后,SuperToken 可以向账号配置的地址发送 HTTP POST 通知。Webhook 是账号级配置,不是单次任务参数;创建 /v1/video/tasks 请求时不要传 webhook_url。
配置步骤
- 进入控制台的“资源管理中心”,打开“Webhook”页签。
- 填写接收通知的公开 HTTP/HTTPS 地址并启用配置,生产环境建议使用 HTTPS。
- 保存生成的
wk-...Webhook Key,并配置到接收服务的安全环境变量中。 - 点击“发送测试”,确认接收端能验证 Key 并返回 HTTP 2xx。
Webhook Key 只用于验证 SuperToken 发出的回调,不具有模型或资源 API 权限。重新生成 Key 后旧 Key 会立即失效,需要同步更新接收服务。
事件与请求头
| 事件 | 触发条件 | 结果位置 |
|---|---|---|
video.task.succeeded | 视频任务成功 | data.object.result.videos[] |
video.task.failed | 视频任务失败 | data.object.error |
webhook.test | 在控制台点击“发送测试” | 用于验证地址和 Webhook Key |
回调请求头:
POST /webhooks/supertoken HTTP/1.1
Authorization: Bearer wk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json接收端必须比较完整的 Authorization 值。当前验证方式是 Bearer Key,不是 HMAC 签名。
成功事件
{
"id": "evt_video_example123",
"object": "event",
"api_version": "2026-07-17",
"type": "video.task.succeeded",
"created_at": 1785373268,
"data": {
"object": {
"id": "task_example123",
"object": "video.task",
"model": "adobe-seedance-2.0-480p",
"operation": "generation",
"status": "succeeded",
"progress": 100,
"result": {
"videos": [
{
"asset_id": "asset_example123",
"index": 0,
"url": "https://media.example.com/results/video.mp4?token=...",
"mime_type": "video/mp4",
"duration_ms": 4000,
"temporary": true,
"url_auth": "none"
}
]
},
"error": null,
"client_reference_id": "order-20260730-001",
"metadata": {
"order_id": "order-20260730-001"
},
"created_at": 1785373200,
"started_at": 1785373202,
"completed_at": 1785373268,
"updated_at": 1785373268
}
}
}data.object 与 GET /v1/video/tasks/{task_id} 返回的任务对象一致。url_auth: "none" 表示应完整保留 URL 查询参数并直接访问;只有值为 resource_api_key 时才使用 ak_... 请求结果 URL。temporary: true 的地址可能过期,平台不会自动刷新。
失败事件
{
"id": "evt_video_example456",
"object": "event",
"api_version": "2026-07-17",
"type": "video.task.failed",
"created_at": 1785373268,
"data": {
"object": {
"id": "task_example456",
"object": "video.task",
"model": "adobe-seedance-2.0-720p",
"operation": "generation",
"status": "failed",
"progress": 100,
"result": null,
"error": {
"code": "reference_media_duration_exceeded",
"message": "reference video and audio must not exceed 15 seconds",
"retryable": false
},
"client_reference_id": "order-20260730-002",
"metadata": {},
"created_at": 1785373200,
"started_at": 1785373202,
"completed_at": 1785373268,
"updated_at": 1785373268
}
}
}参考视频或参考音频无法解析时,失败码为 invalid_reference_media_duration;单项或合计时长 超过所选模型上限时为 reference_media_duration_exceeded。Seedance 2.0 / Fast 的参考视频和 参考音频总时长上限为 15 秒,Seedance 2.5 分别为 30.2 秒。其他上游失败通常使用 video_task_failed。异步失败会进入现有退款流程;实际计价以控制台当前模型配置为准。
Python 接收示例
import os
from secrets import compare_digest
from flask import Flask, abort, request
app = Flask(__name__)
webhook_key = os.environ["SUPERTOKEN_WEBHOOK_KEY"]
@app.post("/webhooks/supertoken")
def receive_video_event():
expected = f"Bearer {webhook_key}"
authorization = request.headers.get("Authorization", "")
if not compare_digest(authorization, expected):
abort(401)
event = request.get_json()
event_id = event["id"]
event_type = event["type"]
task = event["data"]["object"]
# 生产环境应先按 event_id 去重,再把下载、转存等耗时操作放入任务队列。
if event_type == "video.task.succeeded":
videos = task["result"]["videos"]
print(event_id, task["id"], videos)
elif event_type == "video.task.failed":
print(event_id, task["id"], task["error"])
return "", 204
if __name__ == "__main__":
app.run(host="0.0.0.0", port=3000)export SUPERTOKEN_WEBHOOK_KEY="YOUR_WEBHOOK_KEY"
python3 -m pip install flask
python3 webhook.py确认、去重与重试
- 接收端返回任意 HTTP 2xx 即表示投递成功,响应体会被忽略。
- 网络错误、超时或非 2xx 会触发重试。默认总共尝试 3 次、固定间隔 30 秒,实际次数和间隔可能由管理员调整。
- 同一个事件在重试时保持相同的顶层
id。请给事件 ID 建立唯一约束,不要用任务 ID 代替事件去重键。 - 接收端应快速完成鉴权、去重和入队,然后立即返回 2xx;视频下载、转存和订单更新应放在自己的后台任务中。
- 不要在日志中记录完整的
wk-...Key;怀疑泄露时立即重新生成。
Webhook 与轮询可以同时使用。长时间没有收到通知时,使用资源 API Key 查询 /v1/video/tasks/{task_id} 作为兜底;业务处理前也可以再查询一次任务,确认最终状态。
接收端检查清单
| 检查项 | 建议 |
|---|---|
| 来源校验 | 常量时间比较完整的 Authorization: Bearer wk-... |
| 幂等处理 | 使用事件顶层 id 建立唯一约束 |
| 响应时间 | 校验并入队后立即返回 2xx |
| 结果下载 | 根据 url_auth 决定是否发送 ak_... |
| 长期保存 | 下载后转存,不依赖 temporary URL 永久可用 |
| 补偿机制 | 超时未收到事件时查询 /v1/video/tasks/{task_id} |