Sume job.canceled and job.failed webhooks both say status ERROR

A Sume job.failed and a job.canceled webhook both carry status ERROR and an error object. Branch on the event name, not on status, to tell them apart.

4 min readSume
All posts

On a Sume job webhook, a failed job and a canceled job both arrive with status: "ERROR" and an error object. The only field that separates them is event: job.failed or job.canceled. Branch on the event name, and treat status as a coarse OK or ERROR flag.

The three events

Sume sends terminal job events only, so there are exactly three, and no progress or partial deliveries.

Job webhook events (read 2026-10-06)
EventstatusBody carries
job.completedOKpayload.artifacts[]
job.failedERRORerror object
job.canceledERRORerror object

A handler that does not merge them

Cancellation is something you asked for, and failure is not. Keeping them apart matters for retry logic: re-submitting after a cancel you issued is a duplicate, and re-submitting after a failure may be right once you have read the error.

def handle(event: dict) -> str:
    kind = event["event"]
    if kind == "job.completed":
        return "save " + event["payload"]["artifacts"][0]["url"]
    if kind in ("job.failed", "job.canceled"):
        # status is "ERROR" for both, so branch on the event name
        return f"{kind}: {event.get('error')}"
    return "ignore " + kind

print(handle({"event": "job.canceled", "status": "ERROR", "error": {"code": "x"}}))
print(handle({"event": "job.completed", "status": "OK", "payload": {"artifacts": [{"url": "https://media.sume.com/a"}]}}))

Do not use status as the router

Job webhooks and run webhooks share one signature scheme, but the events differ. Run events end in .run.terminal, so route on event there too.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume