Results & Webhooks
To learn that a workflow finished, either poll GetWorkflow until it reaches a terminal state, or register callbacks (webhooks) and let the orchestrator push events to you. For anything that may outlast your HTTP client's timeout, prefer webhooks.
Registering callbacks
Callbacks live on the workflow body you submit via SubmitWorkflow, alongside steps:
POST https://orchestration.civitai.com/v2/consumer/workflows
Authorization: Bearer <your-token>
Content-Type: application/json
{
"steps": [
{
"$type": "videoGen",
"input": { "engine": "wan", "version": "v2.6", "operation": "text-to-video",
"prompt": "...", "resolution": "1080p", "duration": 10 }
}
],
"callbacks": [
{
"url": "https://your-service.example.com/civitai-hooks",
"type": ["workflow:succeeded", "workflow:failed"],
"detailed": true
}
]
}Each entry in callbacks (see the WorkflowCallback schema) has:
| Field | Required | Notes |
|---|---|---|
url | ✅ | HTTPS endpoint that will receive POSTed events. |
type | ✅ | Array of event types to subscribe to (see below). |
detailed | If true, the payload includes the full workflow / step output (blobs, timings). Defaults to false: call GetWorkflow for details. |
A workflow can have several callbacks, with different URLs and event filters.
Event types
Event types use a <scope>:<status> format:
| Scope | Fires on | Typical use |
|---|---|---|
workflow:* | Workflow-level transitions | What you usually want — one event when the whole workflow resolves. |
step:* | Each step transition | Multi-step workflows where you want intermediate output. |
job:* | Each internal job transition | Debugging / observability — usually too noisy for production. |
<status> is any workflow status, or * for all of them. Prefer naming the statuses you act on: * also delivers every preparing progress tick, and any status added later.
Common subscriptions:
- "Tell me when it's done, pass or fail" →
["workflow:succeeded", "workflow:failed", "workflow:expired", "workflow:canceled"] - "Everything about the workflow" →
["workflow:*"] - "Per-step progress in a multi-step pipeline" →
["step:succeeded", "step:failed"] - "Show download progress while models are fetched" →
["step:preparing", "step:processing", "step:succeeded", "step:failed"]—preparingevents carry apreparationobject with the resource being downloaded and its progress.
Event payload
The orchestrator POSTs a JSON body to your url. For workflow-scoped events:
{
"$type": "workflow",
"workflowId": "wf_01HXYZ...",
"status": "succeeded",
"timestamp": "2026-04-12T23:00:00Z",
"details": {
"createdAt": "2026-04-12T22:58:12Z",
"startedAt": "2026-04-12T22:58:14Z",
"completedAt": "2026-04-12T23:00:00Z",
"steps": [
{ "name": "0", "status": "succeeded", "output": { "blobs": [ /* ... */ ] } }
]
}
}details is only present with detailed: true; otherwise you get id, status and timestamp, and fetch the workflow yourself.
Step events use WorkflowStepEvent (workflowId + name + status + optional details), and job events use WorkflowStepJobEvent (adds jobId, progress, reason). Inspect $type to route them.
Delivery semantics
- In-order, serialized per workflow. Each callback invocation completes before the next is sent, so
processingalways arrives beforesucceededfor a given workflow / step. - Terminal states are terminal. Once a workflow or step reaches
succeeded,failed,expired, orcanceled, it never transitions again. preparingandprocessingcan repeat, each signalling progress. For the latest details (e.g. partial output), callGetWorkflow.- Automatic retries on transient errors. If your endpoint returns a non-
2xxor times out, the orchestrator retries before advancing to the next event.
Receiving endpoint checklist
- Return
2xxquickly and do the real work on a queue. A slow receiver delays later events (delivery is serialized) and gets retried. - Be idempotent. Retries and repeated
preparing/processingevents mean an event can arrive more than once. Dedupe on(workflowId, status, timestamp), or treatprocessingas "latest progress, refetch if you care." - Accept only HTTPS URLs — the orchestrator won't post to plain HTTP.
Blobs in results
Each blob object in a step's output carries its own url and urlExpiresAt; where the blobs sit depends on the step type, e.g. output.images[] for imageGen, output.video for videoGen, output.blobs[] for comfy (in a webhook, under details). A signed URL stops working once it passes its urlExpiresAt (a public-tier URL is unsigned: its urlExpiresAt, if set, is when the object is scheduled for deletion, not when a signature lapses). Refetch the workflow, or call GetBlob, for a fresh one (a deleted public-tier object can't be refetched; regenerate it); don't cache the URL. To keep the media, download the bytes and store them yourself.
Polling fallback
If you can't expose a webhook endpoint (scripts, CLI tools, notebooks), poll GetWorkflow at 2 s, 5 s, 10 s, 15 s, then every 30 s, until status is terminal.
Ephemeral workflows
By default a workflow is retained for 30 days after it reaches a terminal state, so you can refetch it via GetWorkflow.
Set "ephemeral": true on the submission body to opt out: the workflow is never written to long-term storage, and the only way to receive its results is a callback or a synchronous wait:
POST https://orchestration.civitai.com/v2/consumer/workflows?wait=90
Content-Type: application/json
{
"ephemeral": true,
"steps": [ /* ... */ ],
"callbacks": [
{ "url": "https://your-service.example.com/civitai-hooks",
"type": ["workflow:succeeded", "workflow:failed"],
"detailed": true }
]
}- Validation. An ephemeral submission with neither a
callbacksentry norwait > 0is rejected with400. - Use
detailed: trueon callbacks (orwaitlong enough for terminal status). While it runs you can pollGetWorkflowas usual, but once it finishesGetWorkflowreturns 404.