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.

5 min readSume
All posts

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

Status words by surface, as of 2026-10-08
WhereWordsNotes
Webhook body statusOK, ERRORFailed and canceled both use ERROR and add an error object
Job status endpointqueued, processing, completed, failed, canceledPlus terminal and result_ready booleans
Queue-shaped status fieldIN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELEDMaps one to one onto sume_status; do not mix them
Webhook delivery statuspending, delivering, delivered, retrying, failed, exhaustedAbout the delivery, not the job
Resource statusprocessing, ready, failed, canceled, archivedAssets 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

All Developers posts

Written by Sume