预览提示 · 本站为上线前预览:模型清单与费率以正式上线公告为准

WEBHOOKS · 免轮询

Webhook 回调:任务完成自动推送

视频生成需要几十秒到几分钟。与其轮询 GET /v1/videos/{id}, 配置一个回调 URL:任务到达终态(成功或失败)时,我们主动 POST 一份结构化事件给你的服务端, 带 HMAC-SHA256 签名。轮询与回调可同时使用,互不冲突。

1. 配置接收端

登录控制台 → 个人设置 → 通知方式:选择 Webhook,填入 Webhook 地址(你的接收端 URL,须为公网可达的 HTTPS 地址)和 Webhook 密钥(自定义字符串,用于验签,妥善保管)。保存后,你的每一个任务终态都会推送到这个地址。

2. 事件载荷

两个事件:task.succeeded(成功)与 task.failed(失败)。POST 请求体为 JSON:

task.succeeded

{
  "event": "task.succeeded",
  "task_id": "vid_7f3a9c",
  "model": "kling-3.0",
  "status": "SUCCESS",
  "outputs": [
    "https://cdn.modapis.com/.../out.mp4"
  ],
  "quota": 42000,
  "credits_consumed": 8.4,
  "submitted_at": 1760851200,
  "finished_at": 1760851268,
  "timestamp": 1760851268
}

task.failed

{
  "event": "task.failed",
  "task_id": "vid_8c1b22",
  "model": "veo-3.1",
  "status": "FAILURE",
  "fail_reason": "upstream capacity exhausted",
  "quota": 0,
  "submitted_at": 1760851300,
  "finished_at": 1760851312,
  "timestamp": 1760851312
}

字段说明

字段类型说明
eventstringtask.succeeded / task.failed
task_idstring任务 ID(提交接口返回的 id,同轮询接口)
modelstring模型名,如 kling-3.0
statusstringSUCCESS / FAILURE
outputsstring[]产物 URL 列表;失败事件无此字段。注意:URL 有时效,请及时转存
quotaint本任务实扣 quota(500000 quota = $1;失败=0)
credits_consumedfloat?上游计费单位(对账用),可能缺失
fail_reasonstring失败原因;成功事件无此字段
submitted_at / finished_atintUnix 时间戳(秒)
timestampint本事件发出时间(秒),可用于新鲜度校验

3. 验证签名(务必做)

每次请求带 X-Webhook-Signature 头:以你配置的密钥为 key、 原始请求体字节为消息的 HMAC-SHA256 hex 值(64 个十六进制字符)。另有 X-Webhook-Event(事件名)与 X-ModAPIS-Timestamp(RFC3339 发出时间)两个头便于路由与重放防护。 注意:务必用原始 body 验签,先 parse 再 stringify 可能因字段顺序/空白不同导致验签失败。

Express
// Node.js:验证 X-Webhook-Signature(express.raw 拿原始字节验签)
import crypto from "node:crypto";
import express from "express";

const app = express();
app.post("/modapis-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const expected = crypto
    .createHmac("sha256", process.env.MODAPIS_WEBHOOK_SECRET)
    .update(req.body) // Buffer:未解析的原始请求体
    .digest("hex");
  if (expected !== req.header("X-Webhook-Signature")) {
    return res.status(401).end();
  }
  const { event, task_id, outputs } = JSON.parse(req.body.toString());
  if (event === "task.succeeded") saveResult(task_id, outputs);
  res.status(200).end();
});
FastAPI
# Python(FastAPI):用原始 body 验签
import hmac, hashlib

@app.post("/modapis-webhook")
async def hook(request: Request):
    raw = await request.body()
    expected = hmac.new(
        os.environ["MODAPIS_WEBHOOK_SECRET"].encode(),
        raw,
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Webhook-Signature", "")):
        raise HTTPException(status_code=401)
    data = await request.json()
    if data["event"] == "task.succeeded":
        save_result(data["task_id"], data["outputs"])
    return {"ok": True}

4. 投递语义

  • 响应约定:接收端返回 2xx 视为成功;其他状态码或超时(5s)视为失败。
  • 重试:失败后最多重试 2 次(共 3 次,间隔约 1s/2s)。三次失败后放弃,该事件不会无限重投——建议关键业务同时保留轮询兜底。
  • 顺序:同一任务只会收到一个终态事件,不保证跨任务的全局顺序。
  • 幂等:极端情况下(如接收端先返回 5xx 后又处理成功)可能收到重复事件,请按 task_id 幂等处理。
  • 安全:接收端应校验签名、限制来源、不要在 URL 中携带凭据。

5. 自测

配置完成后,在已登录的控制台页面打开浏览器开发者工具(Console),执行:

fetch("/api/user/mod/webhook/test", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: "{\\"event\\":\\"task.succeeded\\"}" }).then(r => r.json()).then(console.log)

会向你的接收端发送一条 task_id 为 webhook-test-0000 的合成事件, 返回值里带 status_code(200=送达)与错误信息,方便联调期快速排障。

已复制