APIMaster Async Task Webhooks
Receive signed completion and failure notifications for Seedance, video and asynchronous image tasks, with retries, delivery history and polling fallback.
APIMaster asynchronous task webhooks
Use webhooks as the primary notification mechanism for asynchronous generation, and keep polling as a fallback. APIMaster sends a signed HTTPS POST when a tracked task completes or fails. Notifications are independent of the generation provider and use your APIMaster task ID.
Supported models and endpoints
Seedance includes all four models: seedance-2.0, seedance-2.5, seedance-2.0-fast and seedance-2.0-mini. Use POST /v1/videos/generations or the compatible POST /v1/video/generations.
Video task notifications also support MiniMax-H3, Kling Omni / Motion Control, Grok Imagine Video and Sora. Supported GPT Image 2 / 2.5 and Gemini image workflows use /v1/images/generations/async or supported /v1/images/edits/async; Midjourney v8.2 / Niji 7 use /v1/midjourney/generations. Route availability and generation parameters still follow each model's guide. Synchronous Seedream and Grok image generation, chat streaming and Seedance asset review are not part of this generation webhook contract.
Check GET /v1/webhook-capabilities?model=seedance-2.0-fast for model eligibility. Use a supported asynchronous submission endpoint; eligibility does not mean every image operation or every model alias supports asynchronous generation.
Register and verify an endpoint
Use your API key in Authorization: Bearer YOUR_API_KEY. An API key can manage endpoints and events belonging to its account. Keep it on your server.
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"}'
The response contains endpoint.id, endpoint.key_id and signing_secret. Save the signing secret securely: it is returned only once. It is separate from your API key and cannot authorize generation or downloads. The URL must be a publicly reachable HTTPS endpoint on port 443. Private addresses and redirects are rejected. Up to 20 endpoints are allowed per account; URLs are immutable, so create and verify a new endpoint when changing the destination.
After configuring your receiver, verify ownership:
curl --fail-with-body -X POST "https://apimaster.ai/v1/webhook-endpoints/whep_example/verify" \
-H "Authorization: Bearer YOUR_API_KEY"
APIMaster sends a signed webhook.endpoint_verification event containing data.challenge. Return HTTP 200 with {"challenge":"THE_RECEIVED_CHALLENGE"} within 10 seconds. A generation request referencing an unverified, paused or another account's endpoint is rejected before submission.
The total HTTP request timeout is 10 seconds, including DNS, connection and TLS setup. Response headers must arrive within 8 seconds of sending the request. Persist the event and acknowledge promptly.
Submit a task with notifications
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"
}'
Replace the model ID with any of the four Seedance IDs while keeping its own supported parameters. Save the task ID from the normal creation response. client_reference_id is an optional business reference of at most 128 bytes; it is not a submission idempotency key. Do not combine notifications with legacy webhook or callback_url provider parameters.
For supported multipart image edits, send notifications as a JSON string form field and client_reference_id as a separate form field:
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"
There is no account-wide default endpoint in this version: reference an endpoint in each task request. Webhooks may arrive before your submission response. Persist them even if your application has not yet saved the returned task ID. Some image async workflows wrap synchronous upstream generation, so submission itself can still take time.
Success and failure payloads
A success notification contains the public task ID and an authenticated video link:
{
"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
}
}
Failure uses the same envelope with type: "task.failed", data.status: "failed", data.result: null, and an error object:
{
"code":"generation_failed",
"message":"The generation task failed. Query the task for details."
}
Both provider-confirmed failures and gateway task timeouts can generate failure events. A creation request rejected before a task is accepted does not generate a task event. Notification delivery failure does not change generation status or rerun generation. Events do not confirm that billing reconciliation has finished.
For images, kind is image and outputs contains all available image outputs. Midjourney child task events include batch_id; you can additionally subscribe to batch.completed to receive one batch summary after all children finish. Its status is completed, partial_failure or failed, with counts in data.summary. Batch completion does not always mean every child succeeded.
Download output URLs with an API key belonging to the task's account. Save generated media promptly. expires_at: null means no guaranteed expiry time is provided, not permanent storage; a later download can return HTTP 410. Replaying an event does not extend media availability.
Authenticate callbacks and acknowledge promptly
Each POST carries:
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>
Verify HMAC-SHA256 over the timestamp, a literal dot and the raw request body bytes using the endpoint's signing secret. Use constant-time comparison, allow at most five minutes of clock skew, and check that the Event-ID header matches the body ID. Retries retain the same event ID and body but receive a new delivery ID, timestamp and signature.
The following Python receiver uses Flask and SQLite. It atomically persists an event before acknowledging it. Your application worker should consume the saved rows, download files and run business logic separately.
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
Return any 2xx status within 10 seconds for generation events. Acknowledge after durable storage, without waiting for media downloads. Delivery is at least once: duplicate events are possible, especially if an acknowledgment is lost, so deduplicate by id. There is no ordering guarantee across tasks.
Retries, delivery history and replay
APIMaster retries network failures, acknowledgment timeouts and non-2xx responses for up to 72 hours from event creation. Initial delays are 1, 2, 5, 15 and 30 minutes, then 1, 2, 4 and 8 hours, followed by 12-hour intervals, with up to 20% jitter. Valid Retry-After on 429/503 is respected within the retry window. Redirects are not followed. HTTP 410 pauses the endpoint; other 4xx responses are retried to allow configuration fixes.
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"
Statuses include pending, in_flight, retrying, delivered, paused and exhausted. Event and attempt records are retained for 30 days. Replay preserves the original event ID and payload and opens a new 72-hour delivery window; it does not regenerate media or charge again. Up to five manual replays per event per UTC day are allowed. An event currently leased for delivery cannot be replayed until that attempt ends or its lease expires.
Pause or resume an endpoint with PATCH /v1/webhook-endpoints/{id} and {"enabled":false} or {"enabled":true}. Resume continues paused events whose original deadline has not passed. Rotate secrets with POST /v1/webhook-endpoints/{id}/rotate-secret; save the new secret and kid, and retain the previous pair for the returned 24-hour transition period.
Polling fallback
For tasks without a received terminal event, query /v1/videos/{task_id} or the model's documented image-task query every 60–120 seconds. Use the task API as the source of truth and check delivery history if generation has finished but notification is missing. A missing webhook is not a reason to resubmit a billable generation request. Persistent endpoint downtime can exhaust automatic retries; restore the receiver and replay retained events.
