Failure events
call.failed and call.degraded — opt-in events for calls that never reached completed.
Most calls finish and you get call.completed. Some do not, and
before these two events existed you heard nothing at all about those — which is indistinguishable,
from your side, from a quiet week.
| Event | Fires when | What you still have |
|---|---|---|
call.failed | A required stage exhausted its attempts. The call is quarantined. | Nothing. There is no transcript and no analysis. |
call.degraded | An optional stage failed or was blocked. The call is partially completed. | The transcript and the metrics. Only the optional stage's output is missing. |
Both are opt-in per endpoint — see choosing events.
call.degraded is not a failure, and routing it as one will misinform your users. The call
worked; a piece of enrichment did not. The most common causes are an unset provider key and a
reached budget cap, and in both cases the recording was transcribed and measured normally. That is
why these are two kinds and not one with a status field — route on the kind, and treat
call.degraded as "less than expected", never as "lost".
Which one you get, and when
A call reaches exactly one terminal state, so you receive at most one of these, and never
alongside call.completed — a degraded call never reaches completed.
Each fires once per call. If we retry a quarantined call and it fails again, you do not get a
second call.failed. If a retry succeeds, you get call.completed afterwards, so receiving
call.failed and then call.completed for one call is expected rather than contradictory: read it
as "this recovered".
Duplicate recordings are the one silent case. When identical audio is submitted twice, the second
submission is superseded rather than processed, and no event fires for it — the original call
already sent you its call.completed, so you have the analysis. An event announcing a failure there
would be reporting an incident for a correct outcome.
The payload
Same envelope, same signature, same headers as call.completed.
X-CallLens-Event names the kind, so you can route on the header before parsing the body.
{
"event": "call.degraded",
"occurred_at": "2026-09-08T14:02:55Z",
"delivery": { "idempotency_key": "9c1e4a70-...", "attempt": 1 },
"data": {
"call": {
"reference": "cl_7Yx2bQ...",
"provider_call_id": "PBX-88121",
"idempotency_key": "your-key-abc",
"status": "partially_completed",
"…": "…"
},
"stages": [
{ "stage": "transcribe", "status": "succeeded", "criticality": "hard", "attempts": 1, "correlation_id": null },
{ "stage": "enrich", "status": "blocked", "criticality": "soft", "attempts": 3, "correlation_id": "err_01JB9…" }
]
}
}data.call is the object call.completed publishes under the same name — one shape, documented
once — and provider_call_id and idempotency_key are echoed back exactly as they are there, so
the event joins to your own record the same way.
data.stages
Every stage of the call, in the order they ran, so you can see where it stopped.
criticalityishardorsoft. Ahardstage withstatus: "failed"is what ended acall.failed. Asoftone is what degraded acall.degraded.statusispending,running,succeeded,failed,skippedorblocked.blockedmeans a budget or configuration gate refused the stage rather than the stage erroring.- On
call.degradedthe succeeded stages matter as much as the failed one: they tell you what you do have.
There is no error message, deliberately. A stage's own error text can carry internal detail we
will not send to an address outside our platform. correlation_id is what to quote to support — it
keys the log entry holding the detail this payload does not.
No analysis
Neither event carries data.analysis, including call.degraded, where one often exists. These
events say where the call stopped, not what was found. Publishing a partial analysis under a
degradation notice invites it to be read as a complete one; ask for the call by its reference if you
want what was produced.
What the payload contains
The disclosure on the overview applies in full, though these two carry markedly less: the call's metadata and its pipeline stages, with no model-generated text at all.