Sume job.completed has no error key; job.failed has payload null
A Sume job.completed webhook omits the error key entirely, while job.failed and job.canceled send payload null plus an error. How to branch without a KeyError.

On a Sume job.completed webhook the error key is simply absent, and on job.failed and job.canceled the payload is null and error is present. So read it with a default, such as body.get("error"), and branch on the event field first.
Both failure events carry status ERROR, so the status value cannot tell failed from canceled; the event name can.
The three shapes
These follow the webhook code and docs.
| Event | status | payload | error |
|---|---|---|---|
| job.completed | OK | Result object | Key absent |
| job.failed | ERROR | null | Present |
| job.canceled | ERROR | null | Present |
Why absent, not null
The code leaves error undefined on success, and JSON serialization drops undefined keys. Code that does body["error"] raises KeyError in Python, and code that checks error === null in JavaScript misses it, because the key is undefined, not null. Test for presence or for a falsy value.
A branch that works
Switch on event. For job.completed, take payload. For job.failed, log error and read GET /v1/jobs/{id} for the full record. For job.canceled, mark the work stopped and do not retry automatically, since someone or something chose to cancel it.
Failures refund reserved credits, so a failed event is not a charge you need to dispute.
In Python, a small function that takes the parsed body and returns a tuple of ('ok', payload) or ('error', error) keeps the rest of your code free of key checks. In TypeScript, model the payload as a union keyed on event, so the compiler forces you to handle the missing error key. Whatever the language, write one test per event with a fixture copied from a real delivery, and a fourth test for an unknown event name, which should be rejected after the signature check and not silently accepted.
Tradeoffs
Being strict about the shape breaks on future fields; being lax can hide a malformed delivery. Verify the signature first, then check that event is one of the three you know and reject others loudly.
Keep the raw body you verified, too. If a branch misbehaves later, a stored copy of the exact delivery, with its timestamp header, lets you replay it through the same code and see which shape arrived, which beats guessing from a log line that printed only part of it.
Sources
Related posts
More in Developers
- jobs_wait says status unknown, poll_count 0: is the job lost?
No. A jobs_wait with status unknown and poll_count 0 means the server was too busy to read the job. Wait again on the same ids; never resubmit a paid create.
- Keep a series voice consistent: pin model, voice, speed and volume
A series sounds the same only if every episode sends the same TTS settings. Keep one profile in code, pin a model id, and send it with each Sume request.
- Same volume every Shorts episode: gain_db, duck_db, one music bed
Sume does not measure loudness. To keep a series consistent, pin audio.gain_db, soundtrack gain_db and duck_db in one function with one bed. Python plan loop.
- Kill switch for a Sume video batch: what it stops and what not
A stop file checked between Sume submits halts a batch, but jobs already accepted keep running and billing. A tested Python loop and the cancel limits.
Written by Sume