Skip to content
API documentation

OmnAPI Webhook Events

OmnAPI emits webhooks for three layers of lifecycle:

  • Task-level: task.completed, task.failed, task.cancelled — fired once per task at terminal state.
  • Stage-level: task.stage.started, task.stage.completed, task.stage.failed — emitted for named milestones in supported long-running tasks. Use these events for progress bars or operational dashboards without aggressive polling.
  • Product-level: product-specific events that summarize a resource outcome, such as mv.ready and mv.failed for Music Video jobs.

All event layers fire to the same destination URL. The event type is delivered in the X-Webhook-Event HTTP header. Terminal task bodies are flat public Task descriptors. Stage bodies contain only taskId plus a stage snapshot. MV product events also include a top-level event field with the same value as the header for product-level routing.


  1. Configure a signing secret before accepting events; follow Configure signing.
  2. Add your HTTPS receiver as config.webhookUrl, or the operation’s documented top-level webhookUrl, when creating the task.
  3. Verify the timestamp and signature against the raw request body before parsing it. Store the event’s deduplication key before processing side effects.
  4. Acknowledge promptly, handle duplicate/out-of-order deliveries, and read the Task to reconcile its latest state before treating an output as final.

Start with the downloadable receiver; the headers, event shapes and retry policy below are the shared reference.

Pass webhookUrl in the task creation request:

For a new job, generate and save a key once. Retry with the same route, key, and body.

Terminal window
export REQUEST_KEY="$(uuidgen)"
Terminal window
curl -X POST https://api.omnapi.com/api/v1/suno/songs \
-H "x-api-key: sk_live_..." \
-H "Idempotency-Key: $REQUEST_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "simple",
"prompt": "upbeat lo-fi",
"config": {
"webhookUrl": "https://yourapp.com/webhooks/omnapi"
}
}'

The dashboard also lets you set a default destination for account-level task events. Per-task webhookUrl takes precedence for that one request.


For every event, read the type from X-Webhook-Event. There is no outer { event, task } envelope for task or stage events:

POST /your/webhook HTTP/1.1
Content-Type: application/json
X-Webhook-Event: task.completed

Every delivery includes these headers:

HeaderMeaning
X-Webhook-IdStable event-delivery ID. It does not change across retries; use it as the primary deduplication key.
X-Webhook-EventEvent type such as task.completed or mv.ready.
X-Webhook-TimestampISO timestamp generated for this delivery attempt. It is metadata and is not part of the body-only HMAC.
X-Webhook-AttemptOne-based delivery attempt number.
X-Webhook-SignatureOptional sha256=<hex> HMAC over the exact raw body when a webhook secret is configured.

Terminal task event bodies use the same customer-safe shape as GET /api/v1/tasks/{taskId}:

{
"taskId": "task_01H...",
"status": "COMPLETED",
"creditsRequired": 250,
"creditsCharged": 250,
"refunded": false,
"retryable": false,
"createdAt": "2026-08-08T10:00:00.000Z",
"updatedAt": "2026-08-08T10:03:07.432Z",
"processingStartedAt": "2026-08-08T10:00:01.000Z",
"processingCompletedAt": "2026-08-08T10:03:07.432Z",
"processingDuration": 186432,
"processingDurationText": "3.1m",
"inputParameters": {
"prompt": "..."
},
"outputResults": {
"result": "..."
},
"resources": [],
"warningCodes": []
}

Fired once when a task transitions to COMPLETED. task.outputResults carries the final payload, creditsCharged reflects the actual cost (may be less than creditsRequired after refunds or partial output handling).

Fired once on FAILED (X-Webhook-Event: task.failed). The flat body adds:

{
"taskId": "task_01H...",
"status": "FAILED",
"errorCode": "MV_TOO_MANY_FAILED_SCENES",
"errorMessage": "5/8 scene images failed (threshold 0.3)"
}

Any eligible refund to your credit balance is issued with the status transition. By the time you receive the event, that settlement is already complete.

Fired when a task is cancelled before it completes. Same envelope as task.failed minus errorCode/errorMessage. Refund also atomic.


Supported long-running tasks may emit named stage events. A stage emits started once, followed by either completed or a final failed. An internal retry does not emit another started event or an intermediate failed webhook.

The event type (e.g. task.stage.completed) is in the X-Webhook-Event header. The body contains taskId and a top-level stage block; it is not a full Task descriptor and has no top-level Task status:

{
"taskId": "task_01H...",
"stage": {
"name": "scenes",
"description": "Plan scenes",
"status": "completed",
"startedAt": "...",
"completedAt": "...",
"durationMs": 47213,
"attempt": 1,
"maxAttempts": 2,
"payload": {
"sceneCount": 8,
"genre": "rap",
"mvParadigm": "performance"
}
}
}

stage.payload is stage-specific. Treat unknown keys as metadata and do not build business-critical logic around fields that are not documented for your endpoint.

task.stage.failed is emitted only after the stage exhausts its configured attempts:

{
"taskId": "task_01H...",
"stage": {
"name": "scenes",
"description": "Plan scenes",
"status": "failed",
"startedAt": "2026-08-08T10:00:01.000Z",
"completedAt": "2026-08-08T10:02:01.000Z",
"durationMs": 120000,
"attempt": 2,
"maxAttempts": 2,
"error": {
"code": "STAGE_SCENES_FAILED",
"message": "stage timed out"
}
}
}

A final stage failure does not always mean the Task fails because some stages are optional. Use the later task-level terminal event, or poll the Task, as the authoritative outcome.

Studio storyboard is a public Beta workflow under POST /api/v1/mv with mode="studio". Storyboard tasks may emit this sequence:

#Stage nameTypical duration
1resolve-songUsually <15s
2analyzeUsually <5s
3emotion5-30s
4concept5-30s
5character-anchor15-40s when a character anchor is needed
6narrative5-30s
7scenes10-60s
8scene-images60-240s

Followed by the task-level task.completed. Total wall-clock 3-10min for a typical 60-second song.

Scene-render tasks usually complete with task-level events only.

Finalize tasks usually complete with task-level events only.


MV create and finalize tasks may emit product-level events after the MV resource state is known.

EventWhen it fires
mv.readyAn MV task produced a ready MV resource or final video reference.
mv.failedAn MV task failed and the MV resource cannot advance without a new request.

The event type is still delivered in X-Webhook-Event. The JSON body includes the same event value and MV-specific routing fields:

POST /your/webhook HTTP/1.1
Content-Type: application/json
X-Webhook-Event: mv.ready
{
"event": "mv.ready",
"taskId": "task_01H...",
"status": "COMPLETED",
"mode": "fast",
"mvId": "mv_01H...",
"finalMvId": "final_01H...",
"finalStatus": "READY",
"deliveryStatus": "ready",
"viewUrl": "/api/v1/mv/mv_01H...",
"finalUrl": "/api/v1/mv/mv_01H.../final"
}

Treat missing optional fields as “not available yet” rather than as a schema error. For example, finalUrl is only present when a final MP4 can be requested.

OmnAPI delivers up to 4 concurrent requests and 600 requests per 60 seconds to one destination by default. Destinations share capacity when their URL protocol, host, and path match; query parameters do not create separate capacity. An event waiting for destination capacity does not consume a delivery attempt. Delivery may arrive later during bursts or retries; these limits are not a delivery-time guarantee. Acknowledge promptly and process events asynchronously.

GuaranteeStrength
HTTP arrival order, including stage boundaries from one taskNo guarantee. Delivery jobs run independently and retries can overtake later events.
Across tasks: events arrive in any orderNo guarantee.
Exactly-once deliveryAt-least-once — retries can re-deliver. Dedupe by the stable X-Webhook-Id.

OmnAPI makes one initial webhook delivery attempt and then up to 5 retries with backoff 10s → 20s → 40s → 10min → 30min. HTTP 2xx is treated as success. After the final attempt the delivery is visible in the dashboard’s Webhook Events page where you can manually re-trigger.


  1. In Dashboard → Webhooks, create or update your HTTPS destination and enable delivery.
  2. Arrange a signing secret for that destination with OmnAPI support. The current dashboard URL editor does not expose a signing-secret field, and the public task config accepts webhookUrl, not a per-request secret.
  3. Store the agreed secret in your receiver’s environment. Verify X-Webhook-Signature against the exact raw request bytes before parsing JSON.
  4. Confirm a real task event is signed and accepted before enabling automated fulfillment. A missing signature is not proof of authenticity.

A task-level webhookUrl overrides the destination. An enabled signing configuration for that exact URL takes precedence; otherwise the enabled account-default signing secret is used. Merely adding a task URL does not create a secret. Use only destinations you control and verify the configuration when changing a URL.

Coordinate secret rotation with support and your receiver. Temporarily accept both configured old and new secrets for outstanding deliveries, then remove the old secret after those deliveries settle. Deduplicate using X-Webhook-Id; secret rotation must not process one event twice.

Use HTTPS webhook URLs on domains you control. Verify signatures when a webhook secret is configured, dedupe on X-Webhook-Id, and reject duplicate deliveries in your application. For higher-security deployments, combine signature verification with your own allowlist or gateway rules.

If you configure a webhook secret, OmnAPI sends X-Webhook-Signature: sha256=<hex> where the hex value is HMAC-SHA256 over the raw request body. A minimal Node receiver is available here:

Download webhook-receiver-node.ts


  1. Acknowledge fast. Respond 2xx within 10s, then continue heavier work asynchronously in your application.
  2. Dedupe by X-Webhook-Id. The value remains stable across every delivery attempt for one event.
  3. Use task-level events for billing logic. Billing settles at task-level; stage events are for UX and observability.
  4. Subscribe to task.stage.completed, not task.stage.started unless you’re building a live progress bar. Started events are noisy.
  5. Read X-Webhook-Event first. Task and stage bodies do not include an event field. MV product bodies include it as a convenience, but the header is still the routing source of truth.

The Node/Bun receiver requires a configured webhook secret, validates the exact raw body, rejects malformed JSON, and bounds request size. Run it with OMNAPI_WEBHOOK_SECRET set to the same secret as your OmnAPI webhook configuration. Its in-memory duplicate cache is a local example: replace it with durable event storage and idempotent processing in production. Only the body is signed; do not authorize business effects from delivery headers alone. Confirm the task and its ownership when applying an event.