APIMaster.ai

APIMaster 异步任务 Webhook

接收 Seedance、视频和异步图像任务的签名完成与失败通知,支持重试、投递历史记录和轮询回退机制。

APIMaster 异步任务 Webhook

将 Webhook 作为异步生成的主要通知机制,并保留轮询作为备用方案。当被跟踪的任务完成或失败时,APIMaster 会发送一个签名的 HTTPS POST 请求。通知与生成服务提供商无关,并使用您的 APIMaster 任务 ID。

支持的模型和端点

Seedance 包含全部四个模型:seedance-2.0、seedance-2.5、seedance-2.0-fast 和 seedance-2.0-mini。请使用 POST /v1/videos/generations 或兼容的 POST /v1/video/generations。

视频任务通知还支持 MiniMax-H3、Kling Omni / Motion Control、Grok Imagine Video 和 Sora。支持的 GPT Image 2 / 2.5 和 Gemini 图像工作流使用 /v1/images/generations/async 或支持的 /v1/images/edits/async;Midjourney v8.2 / Niji 7 使用 /v1/midjourney/generations。路由可用性和生成参数仍需遵循每个模型的指南。同步的 Seedream 和 Grok 图像生成、聊天流式传输以及 Seedance 资产审核不属于此生成 Webhook 合约的一部分。

请查阅 GET /v1/webhook-capabilities?model=seedance-2.0-fast 以了解模型资格。请使用受支持的异步提交端点;具备资格并不意味着每个图像操作或每个模型别名都支持异步生成。

注册并验证端点

在 Authorization: Bearer YOUR_API_KEY 中使用您的 API 密钥。一个 API 密钥可以管理其所属账户的端点和事件。请将其保存在您的服务器上。

curl --fail-with-body "https://apimaster.ai/v1/webhook-endpoints" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-server.example/webhooks/apimaster"}'

响应包含 endpoint.id、endpoint.key_id 和 signing_secret。请安全地保存签名密钥:它仅返回一次。它与您的 API 密钥是分开的,不能用于授权生成或下载。URL 必须是公开可达的 HTTPS 端点,端口为 443。私有地址和重定向会被拒绝。每个账户最多允许 20 个端点;URL 是不可变的,因此更改目标地址时,请创建并验证一个新端点。

配置好您的接收器后,验证所有权:

curl --fail-with-body -X POST "https://apimaster.ai/v1/webhook-endpoints/whep_example/verify" \
  -H "Authorization: Bearer YOUR_API_KEY"

APIMaster 会发送一个签名的 webhook.endpoint_verification 事件,其中包含 data.challenge。请在 10 秒内返回 HTTP 200 状态码及 {"challenge":"THE_RECEIVED_CHALLENGE"}。引用未验证、已暂停或属于其他账户的端点的生成请求将在提交前被拒绝。

HTTP 请求总超时为 10 秒(包括 DNS、连接和 TLS 握手),发送请求后等待响应头的上限为 8 秒。接收端应持久化事件后立即确认。

提交带通知的任务

curl --fail-with-body "https://apimaster.ai/v1/videos/generations" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model":"seedance-2.5",
    "prompt":"A paper boat floating on a calm pond, slow camera movement.",
    "duration":4,
    "resolution":"480p",
    "aspect_ratio":"16:9",
    "notifications":{
      "webhook":{
        "endpoint_id":"whep_example",
        "events":["task.completed","task.failed"]
      }
    },
    "client_reference_id":"order_123"
  }'

将模型 ID 替换为四个 Seedance ID 中的任意一个,同时保留其自身支持的参数。请保存正常创建响应中的任务 ID。client_reference_id 是一个可选的业务参考标识,最多 128 字节;它不是提交幂等性密钥。请勿将 notifications 与旧的 webhook 或 callback_url 提供商参数结合使用。

对于支持的多部分图像编辑,请将 notifications 作为 JSON 字符串表单字段发送,并将 client_reference_id 作为单独的表单字段发送:

curl --fail-with-body "https://apimaster.ai/v1/images/edits/async" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "model=gpt-image-2" \
  -F "prompt=Turn the reference into a watercolor illustration" \
  -F "image=@reference.png" \
  -F 'notifications={"webhook":{"endpoint_id":"whep_example","events":["task.completed","task.failed"]}}' \
  -F "client_reference_id=image_order_123"

此版本中没有账户级别的默认端点:需要在每个任务请求中引用一个端点。Webhook 可能在您的提交响应返回之前到达。即使您的应用程序尚未保存返回的任务 ID,也请持久化这些通知。某些图像异步工作流包装了同步的上游生成,因此提交本身可能仍需要时间。

成功和失败负载

成功通知包含公共任务 ID 和一个经过身份验证的视频链接:

{
  "id":"evt_example",
  "type":"task.completed",
  "api_version":"2026-10-03",
  "created_at":1791014400,
  "data":{
    "task_id":"task_example",
    "model":"seedance-2.5",
    "kind":"video",
    "status":"completed",
    "client_reference_id":"order_123",
    "completed_at":1791014400,
    "task_url":"https://apimaster.ai/v1/videos/task_example",
    "result":{
      "outputs":[{
        "output_id":"0",
        "type":"video",
        "url":"https://apimaster.ai/v1/videos/task_example/content",
        "authentication":"bearer",
        "expires_at":null
      }]
    },
    "error":null
  }
}

失败通知使用相同的信封结构,包含 type: "task.failed"、data.status: "failed"、data.result: null 和一个错误对象:

{
  "code":"generation_failed",
  "message":"The generation task failed. Query the task for details."
}

提供商确认的失败和网关任务超时都可能生成失败事件。在任务被接受之前就被拒绝的创建请求不会生成任务事件。通知投递失败不会改变生成状态或重新运行生成。事件不确认账单对账是否已完成。

对于图像,kind 为 image,且 outputs 包含所有可用的图像输出。Midjourney 子任务事件包含 batch_id;您还可以额外订阅 batch.completed,以便在所有子任务完成后接收一个批次摘要。其状态为 completed、partial_failure 或 failed,计数位于 data.summary 中。批次完成并不总是意味着每个子任务都成功。

请使用属于任务账户的 API 密钥下载输出 URL。请及时保存生成的媒体。expires_at: null 意味着未提供保证的过期时间,而非永久存储;后续下载可能返回 HTTP 410。重放事件不会延长媒体的可用性。

验证回调并及时确认

每次POST请求包含:

X-APIMaster-Event-ID: evt_example
X-APIMaster-Delivery-ID: dlv_example
X-APIMaster-Timestamp: 1791014400
X-APIMaster-Signature: v1=<hex-hmac>,kid=<key-id>

使用端点的签名密钥,对时间戳、一个文字点号和原始请求体字节进行HMAC-SHA256验证。使用恒定时间比较,最多允许五分钟的时钟偏差,并检查Event-ID标头是否与正文中的ID匹配。重试会保留相同的事件ID和正文,但会获得新的投递ID、时间戳和签名。

以下Python接收器使用Flask和SQLite。它在确认事件之前以原子方式持久化事件。您的应用程序工作进程应单独消费已保存的行、下载文件并执行业务逻辑。

import hashlib, hmac, json, os, sqlite3, time
from flask import Flask, request, jsonify, abort

app = Flask(__name__)
# During key rotation, configure both active and previous kid/secret pairs.
SECRETS = json.loads(os.environ["APIMASTER_WEBHOOK_SECRETS_JSON"])
DB_PATH = os.environ.get("WEBHOOK_DB_PATH", "webhook-events.sqlite3")
with sqlite3.connect(DB_PATH) as db:
    db.execute("CREATE TABLE IF NOT EXISTS events (id TEXT PRIMARY KEY, body BLOB NOT NULL)")

@app.post("/webhooks/apimaster")
def receive():
    raw = request.get_data()
    try:
        timestamp = request.headers["X-APIMaster-Timestamp"]
        if abs(time.time() - int(timestamp)) > 300:
            abort(401)
        fields = dict(part.split("=", 1) for part in
                      request.headers["X-APIMaster-Signature"].split(","))
        secret = SECRETS[fields["kid"]]
        expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw,
                            hashlib.sha256).hexdigest()
        if not hmac.compare_digest(expected, fields["v1"]):
            abort(401)
        event = json.loads(raw)
        if event["id"] != request.headers["X-APIMaster-Event-ID"]:
            abort(401)
    except (KeyError, ValueError, TypeError):
        abort(401)
    if event["type"] == "webhook.endpoint_verification":
        return jsonify(challenge=event["data"]["challenge"])
    with sqlite3.connect(DB_PATH) as db:
        db.execute("INSERT OR IGNORE INTO events(id, body) VALUES (?, ?)",
                   (event["id"], raw))
    return "", 204

对于生成事件,请在10秒内返回任何2xx状态。在持久化存储后立即确认,无需等待媒体下载。投递是至少一次:重复事件是可能的,特别是在确认丢失的情况下,因此需要通过id进行去重。跨任务没有顺序保证。

重试、投递历史与重放

APIMaster会对网络故障、确认超时和非2xx响应进行重试,最长持续自事件创建起72小时。初始延迟为1、2、5、15和30分钟,然后是1、2、4和8小时,接着是12小时间隔,最多有20%的抖动。在重试窗口内,429/503状态码上的有效Retry-After会被遵守。不跟随重定向。HTTP 410会暂停端点;其他4xx响应会被重试,以便允许配置修复。

curl --fail-with-body "https://apimaster.ai/v1/webhook-events?task_id=task_example" \
  -H "Authorization: Bearer YOUR_API_KEY"

curl --fail-with-body "https://apimaster.ai/v1/webhook-events/evt_example/deliveries" \
  -H "Authorization: Bearer YOUR_API_KEY"

curl --fail-with-body -X POST "https://apimaster.ai/v1/webhook-events/evt_example/redeliver" \
  -H "Authorization: Bearer YOUR_API_KEY"

状态包括pending、in_flight、retrying、delivered、paused和exhausted。事件和尝试记录会保留30天。重放会保留原始事件ID和负载,并开启一个新的72小时投递窗口;它不会重新生成媒体或再次收费。每个UTC日每个事件最多允许五次手动重放。当前已租约用于投递的事件,在该次尝试结束或其租约到期之前无法重放。

使用PATCH /v1/webhook-endpoints/{id}和{"enabled":false}或{"enabled":true}来暂停或恢复端点。恢复会继续处理那些原始截止时间尚未过期的已暂停事件。使用POST /v1/webhook-endpoints/{id}/rotate-secret轮换密钥;保存新的密钥和kid,并在返回的24小时过渡期内保留前一对密钥。

轮询后备方案

对于未收到终端事件的任务,每60-120秒查询一次/v1/videos/{task_id}或模型文档中记录的图像任务查询。使用任务API作为事实来源,如果生成已完成但通知缺失,请检查投递历史。缺少webhook不是重新提交可计费生成请求的理由。持续的端点停机可能会耗尽自动重试;请恢复接收器并重放保留的事件。