视频 Webhook
统一异步视频任务进入终态后,SuperToken 可以向账号配置的公开地址发送成功或失败通知。Kling、Veo、Seedance 等使用 /v1/video/tasks 的任务采用相同事件结构。
Webhook 是账号级设置。创建任务时不要传 webhook_url。
配置
- 打开控制台“资源管理中心 → Webhook”。
- 填写可从公网访问的 HTTPS 接收地址。
- 生成并保存
wk-...Webhook Key。 - 启用配置并发送一次测试事件。
Webhook Key 只用于接收端验证通知来源,不能创建任务或查询资源。重新生成后旧 Key 会立即失效。
请求鉴权
每次通知都带:
http
Authorization: Bearer wk-...
Content-Type: application/json接收端应使用恒定时间比较验证完整 Authorization 值。验证失败返回 401 或 403;验证成功后尽快返回任意 2xx。
事件类型
| 事件 | 触发条件 |
|---|---|
video.task.succeeded | 视频任务成功并产生公开结果 |
video.task.failed | 视频任务进入失败终态 |
webhook.test | 控制台发送测试通知 |
Webhook 可能因为网络错误、超时或非 2xx 响应重复投递。业务处理必须按事件 id 去重。
成功事件
data.object 与 GET /v1/video/tasks/{task_id} 返回的任务对象一致:
json
{
"id": "evt_video_example",
"object": "event",
"api_version": "2026-07-17",
"type": "video.task.succeeded",
"created_at": 1785373268,
"data": {
"object": {
"id": "task_example123",
"object": "video.task",
"model": "adobe-kling-3.0-720p",
"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": 3000,
"temporary": true,
"url_auth": "none"
}
]
},
"error": null,
"client_reference_id": "order-kling-001",
"metadata": {
"order_id": "order-kling-001"
},
"created_at": 1785373200,
"started_at": 1785373202,
"completed_at": 1785373268,
"updated_at": 1785373268
}
}
}处理视频 URL 时读取 url_auth:
none:直接访问 URL,不附加 SuperToken Key。resource_api_key:访问 URL 时携带Authorization: Bearer ak_...。
temporary: true 表示 URL 不应被当作永久地址。临时签名 URL 过期后不会自动刷新;不要把完整签名查询参数写入普通日志。
当前 duration_ms 回显已验证的请求时长,不表示 SuperToken 下载结果媒体重新探测了真实时长,也不会触发二次计费。
失败事件
json
{
"id": "evt_video_failed_example",
"object": "event",
"api_version": "2026-07-17",
"type": "video.task.failed",
"created_at": 1785373268,
"data": {
"object": {
"id": "task_failed_example",
"object": "video.task",
"model": "adobe-veo-3.1-fast-720p",
"operation": "generation",
"status": "failed",
"progress": 100,
"result": null,
"error": {
"code": "video_task_failed",
"message": "Video task failed",
"retryable": false
},
"client_reference_id": "order-veo-001",
"metadata": {},
"created_at": 1785373200,
"started_at": 1785373202,
"completed_at": 1785373268,
"updated_at": 1785373268
}
}
}失败通知不会包含内部路由、执行账号、钱包余额或上游鉴权信息。
Python 接收示例
下面使用 Flask 验证 Key、按事件 ID 去重,并快速返回 204:
python
import hmac
import os
from flask import Flask, abort, request
app = Flask(__name__)
webhook_key = os.environ["SUPERTOKEN_WEBHOOK_KEY"]
expected_authorization = f"Bearer {webhook_key}"
# 生产环境请使用带唯一约束的持久化表替代内存集合。
processed_event_ids = set()
@app.post("/webhooks/supertoken")
def receive_video_webhook():
actual = request.headers.get("Authorization", "")
if not hmac.compare_digest(actual, expected_authorization):
abort(401)
event = request.get_json(force=True)
event_id = event.get("id")
event_type = event.get("type")
if not event_id:
abort(400)
if event_id in processed_event_ids:
return "", 204
if event_type == "video.task.succeeded":
task = event["data"]["object"]
for video in task["result"]["videos"]:
print(
"video ready:",
task["id"],
video["asset_id"],
video["url_auth"],
)
elif event_type == "video.task.failed":
task = event["data"]["object"]
print("video failed:", task["id"], task["error"])
elif event_type != "webhook.test":
abort(400)
# 生产环境应在同一个数据库事务中完成业务写入和事件去重。
processed_event_ids.add(event_id)
return "", 204运行:
bash
export SUPERTOKEN_WEBHOOK_KEY="YOUR_WEBHOOK_KEY"
flask --app webhook run --host 0.0.0.0 --port 8000Node.js 接收示例
下面只使用 Node.js 内置模块:
js
import { timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";
const webhookKey = process.env.SUPERTOKEN_WEBHOOK_KEY;
if (!webhookKey) throw new Error("SUPERTOKEN_WEBHOOK_KEY is required");
const expected = Buffer.from(`Bearer ${webhookKey}`);
const processed = new Set();
function authorized(value = "") {
const actual = Buffer.from(value);
return actual.length === expected.length && timingSafeEqual(actual, expected);
}
createServer((request, response) => {
if (request.method !== "POST" || request.url !== "/webhooks/supertoken") {
response.writeHead(404).end();
return;
}
if (!authorized(request.headers.authorization)) {
response.writeHead(401).end();
return;
}
const chunks = [];
request.on("data", (chunk) => chunks.push(chunk));
request.on("end", () => {
try {
const event = JSON.parse(Buffer.concat(chunks).toString("utf8"));
if (!event.id) throw new Error("event id is required");
if (!processed.has(event.id)) {
const task = event.data?.object;
console.log(event.type, task?.id, task?.status);
processed.add(event.id);
}
response.writeHead(204).end();
} catch {
response.writeHead(400).end();
}
});
}).listen(8000);生产环境应使用数据库唯一键持久化事件 ID,不能依赖进程内 Set。
轮询兜底
Webhook 与轮询可以同时使用。通知延迟或长时间未到达时,使用资源 API Key 查询:
bash
curl -sS "$SUPERTOKEN_BASE_URL/v1/video/tasks/$TASK_ID" \
-H "Authorization: Bearer $RESOURCE_API_KEY" |
jq .收到 Webhook 后也可以再次查询任务,以当前公开任务对象为最终状态依据。