Skip to content
SuperToken 文档

视频 Webhook

统一异步视频任务进入终态后,SuperToken 可以向账号配置的公开地址发送成功或失败通知。Kling、Veo、Seedance 等使用 /v1/video/tasks 的任务采用相同事件结构。

Webhook 是账号级设置。创建任务时不要传 webhook_url

配置

  1. 打开控制台“资源管理中心 → Webhook”。
  2. 填写可从公网访问的 HTTPS 接收地址。
  3. 生成并保存 wk-... Webhook Key。
  4. 启用配置并发送一次测试事件。

Webhook Key 只用于接收端验证通知来源,不能创建任务或查询资源。重新生成后旧 Key 会立即失效。

请求鉴权

每次通知都带:

http
Authorization: Bearer wk-...
Content-Type: application/json

接收端应使用恒定时间比较验证完整 Authorization 值。验证失败返回 401403;验证成功后尽快返回任意 2xx

事件类型

事件触发条件
video.task.succeeded视频任务成功并产生公开结果
video.task.failed视频任务进入失败终态
webhook.test控制台发送测试通知

Webhook 可能因为网络错误、超时或非 2xx 响应重复投递。业务处理必须按事件 id 去重。

成功事件

data.objectGET /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 8000

Node.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 后也可以再次查询任务,以当前公开任务对象为最终状态依据。

下一步

SuperToken - 让全球顶级 AI 模型触手可达