FAL 格式 Webhook

FAL 格式接口创建的是异步任务。除了轮询任务状态,你还可以在提交任务时提供 Webhook 地址,由 Modelink 在任务状态变化时向该地址发送 POST 请求。

Webhook 适合生成图片、视频等耗时较长的任务。接收端返回 2xx 时,Modelink 会将本次投递判定为成功;请求超时或返回非 2xx 状态码时会按重试策略再次投递。

信息

本页说明 Modelink 的 FAL 兼容 Webhook。载荷主体与 FAL 格式兼容;Modelink 还会发送 IN_PROGRESS 进度回调,这是相对 FAL 官方终态回调的扩展。

配置回调地址

FAL 格式接口统一通过 Query 参数 fal_webhook 配置回调地址,这是当前推荐的调用方式。

调用 FAL 格式接口时,鉴权请求头使用 Authorization: Key <api_key>,与 FAL 官方格式保持一致。

回调地址必须满足以下要求:

  • 使用 https://
  • 能从公网访问,不能指向本机、内网或其他受限地址。
  • 不要依赖 Cookie、浏览器登录态或 Modelink API Key。
  • 不要使用会跳转到其他地址的 URL;Webhook 投递不会跟随重定向。

下面以视频生成接口为例,通过 fal_webhook 指定回调地址。Query 参数中的 URL 必须进行 URL 编码:

export MODELINK_API_KEY="替换为你的 Modelink API Key"
export MODELINK_BASE_URL="https://api.qnaigc.com"

curl --request POST \
  --url "$MODELINK_BASE_URL/queue/bytedance/seedance-2.0/text-to-video?fal_webhook=https%3A%2F%2Fexample.com%2Fwebhooks%2Fmodelink" \
  --header "Authorization: Key $MODELINK_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "prompt": "A paper boat drifting through a rainy neon city"
  }'

任务提交成功后,接口仍会立即返回 IN_QUEUErequest_id。请保存 request_id,它既用于查询任务,也用于关联后续 Webhook。

回调请求

Modelink 使用 Content-Type: application/json 向你的地址发送 POST 请求。通用结构如下:

字段类型说明
request_idstring任务 ID;与创建任务时返回的 request_id 一致
gateway_request_idstringFAL 兼容字段;当前与 request_id 相同
statusstringIN_PROGRESSOKERROR
payloadobject | null任务结果、错误详情;IN_PROGRESS 时为 null
errorstring失败原因,仅 ERROR 回调中可能出现
payload_errorstringFAL 兼容的可选字段;结果无法序列化为 JSON 时可能出现,不应作为固定字段依赖

Webhook 的 status 与查询接口的队列状态不是同一组枚举:

Webhook 状态含义payload
IN_PROGRESS任务正在处理null
OK任务成功完成模型结果,例如 imagesvideo
ERROR任务失败或未能完成错误详情,通常包含 detail;同时可能提供 error

处理中

同一个任务可能在进入处理、下载或上传等阶段时收到 IN_PROGRESS 回调:

{
  "request_id": "qvideo-user-1770000000000000000",
  "gateway_request_id": "qvideo-user-1770000000000000000",
  "status": "IN_PROGRESS",
  "payload": null
}

成功结果

图片任务成功时,payload 通常包含 images

{
  "request_id": "qimage-user-1770000000000000000",
  "gateway_request_id": "qimage-user-1770000000000000000",
  "status": "OK",
  "payload": {
    "images": [
      {
        "url": "https://example-cdn.com/result/image.png",
        "content_type": "image/png",
        "file_name": "image.png",
        "file_size": 1824075,
        "width": 1024,
        "height": 1024
      }
    ]
  }
}

视频任务成功时,payload 通常包含 video

{
  "request_id": "qvideo-user-1770000000000000000",
  "gateway_request_id": "qvideo-user-1770000000000000000",
  "status": "OK",
  "payload": {
    "video": {
      "url": "https://example-cdn.com/result/video.mp4",
      "content_type": "video/mp4",
      "file_name": "video.mp4",
      "file_size": 8482016,
      "duration": 5
    }
  }
}

不同模型的结果字段可能不同。解析 payload 时应以对应接口的结果响应结构为准,并允许增加新字段。结果 URL 通常有有效期,收到成功回调后应尽快转存到自己的受控存储。

失败结果

任务失败时,statusERRORerror 提供简要原因,payload.detail 提供结构化错误详情:

{
  "request_id": "qvideo-user-1770000000000000000",
  "gateway_request_id": "qvideo-user-1770000000000000000",
  "status": "ERROR",
  "error": "prompt is required",
  "payload": {
    "detail": [
      {
        "loc": ["body", "prompt"],
        "msg": "field required",
        "type": "value_error.missing",
        "url": ""
      }
    ]
  }
}

detail 需要按宽松结构处理:不同错误来源可能返回数组、对象或字符串。

接收与幂等处理

Webhook 采用至少一次投递语义:网络错误、超时或非 2xx 响应都可能触发重复投递。建议接收端遵循以下流程:

  1. 读取并校验 JSON,记录 request_idstatus 和接收时间。
  2. 使用 request_id + status 作为幂等键,重复回调直接返回 2xx
  3. 将耗时的下载、转存和业务处理放入自己的任务队列。
  4. 返回 2xx 确认接收;下载、转存等耗时业务由接收端自行调度。
  5. 只允许任务状态向前推进;如果终态后又收到较早的 IN_PROGRESS,应忽略该状态回退。

以下是一个精简的 Node.js 接收示例:

import { createServer } from "node:http";

const received = new Set(); // 生产环境请使用数据库或持久化缓存

createServer((request, response) => {
  if (request.method !== "POST") {
    response.writeHead(405).end();
    return;
  }

  let rawBody = "";
  request.on("data", (chunk) => {
    rawBody += chunk;
  });
  request.on("end", () => {
    try {
      const event = JSON.parse(rawBody);
      const key = `${event.request_id}:${event.status}`;

      if (!received.has(key)) {
        received.add(key);
        // 把 event 写入队列,在后台处理 payload。
      }

      response.writeHead(204).end();
    } catch {
      response.writeHead(400).end();
    }
  });
}).listen(3000);

超时与重试

Modelink 的当前投递策略如下:

  • 单次请求超时为 30 秒。
  • 2xx 表示接收成功;网络错误、超时、重定向和其他 HTTP 状态码均视为失败。
  • 首次失败后最多重试 3 次,间隔约为 2 秒、4 秒和 8 秒,即最多投递 4 次。

Webhook 不能替代任务查询接口。若业务要求可靠收敛,建议保存创建任务时返回的 request_id,并对长时间未收到终态回调的任务进行低频补偿查询。

安全建议

  • 在回调 URL 路径中加入不可预测的随机令牌,例如 /webhooks/modelink/<random-token>,并定期轮换。
  • 对请求体设置大小上限,只接受 POSTapplication/json
  • 不要仅凭媒体 URL 的域名判断回调可信,也不要在日志中完整记录带临时凭证的结果 URL。
  • 当前 Modelink 回调不提供 FAL 官方的 X-Fal-Webhook-* 签名请求头,不要直接套用 FAL 官方签名校验流程。
  • 需要更严格鉴权时,可在自己的接收层使用独立域名、随机路径、来源网络策略和后续任务查询进行交叉校验。

参考