Sume webhook status is OK or ERROR; job status is completed or failed
A Sume job webhook body says status OK or ERROR, while the job endpoints say completed, failed or canceled. Branch on the event name and map both vocabularies.

The status field inside a Sume job webhook body is OK or ERROR. The job endpoints use a different word list: queued, processing, completed, failed and canceled. They are not the same field, so a check such as status == "completed" on a webhook body never matches. Branch on the event name (job.completed, job.failed, job.canceled) and use status only as a cross-check.
Where each vocabulary appears
| Where | Words | Notes |
|---|---|---|
| Webhook body status | OK, ERROR | Failed and canceled both use ERROR and add an error object |
| Job status endpoint | queued, processing, completed, failed, canceled | Plus terminal and result_ready booleans |
| Queue-shaped status field | IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELED | Maps one to one onto sume_status; do not mix them |
| Webhook delivery status | pending, delivering, delivered, retrying, failed, exhausted | About the delivery, not the job |
| Resource status | processing, ready, failed, canceled, archived | Assets and other resources |
Why it bites
The word failed appears twice with different meanings. A job can be completed while its webhook delivery is exhausted, after ten refused attempts. The job result is fine and your endpoint missed it. Likewise ERROR in a webhook body covers both a failure and a cancel, so you cannot tell them apart from status alone. The event name can.
A mapper
The function maps a webhook body to your internal state and refuses to guess on an unknown event. Pair it with deduplication on job_id.
def internal_state(body: dict) -> str:
event = body.get("event")
status = body.get("status")
if event == "job.completed" and status == "OK":
return "done"
if event == "job.failed" and status == "ERROR":
return "failed"
if event == "job.canceled":
return "canceled"
if event in {"job.completed", "job.failed"}:
return "inconsistent" # event and status disagree: poll the job
return "ignore"
print(internal_state({"event": "job.completed", "status": "OK"}))
print(internal_state({"event": "job.canceled", "status": "ERROR"}))
print(internal_state({"event": "webhook.test"}))When they disagree
If the event and the status contradict each other, do not trust either. Read GET /v1/jobs/{job_id}/status and use its terminal and sume_status fields. The job record is the source of truth, and the webhook is a delivery optimization.
Store both, trust one
Keep the raw event name, the webhook status, and the job sume_status from the last poll in separate columns. When you debug a mismatch, the three values tell you whether the problem is in delivery, in your parser or in the job. Use the event name for routing and the job record for the final answer.
This is also why the delivery status vocabulary matters. exhausted means Sume used all ten attempts and you have not been told. The job may be completed, and the correct response is to read the result or ask for a redelivery.
A completed event carries payload.artifacts[] with id, url, type and content_type. Use those Sume media URLs, not provider URLs, which are not public API outputs. A failed or canceled event carries an error object instead. Check for the presence of error before you read artifacts, so a canceled job does not raise on a missing list.
Sources
Related posts
More in Developers
- Swap the Sume video model with an env var and a catalog check in Node
Read the model id from VIDEO_MODEL, confirm it appears in GET /v1/videos/models, and fall back to sume/auto when it does not. A Node 20 script of 21 lines.
- Swift: URLSession async/await for one 30-second Wan 3.0 job
A 28-line main.swift that submits wan-3.0 for 30 seconds, polls with Task.sleep and saves the MP4. Runs on macOS or Linux with swiftc.
- Switch video models by changing one string: what can still break
On Sume's /v1/videos you swap the model id and keep the body. Duration range, resolution and aspect ratio are the three fields that may need adjusting.
- sync, subscribe, async or webhook: which Sume mode for a video job
Sume's sync and subscribe modes wait at most 30 seconds, then return a job id. Use async or webhook for video; Python that survives a timed-out wait.
Written by Sume