Severity events
call.risk.high, call.policy.breached and call.score.low — opt-in alerts on what an analysis found.
Three events say something in this call needs a look, without making you parse the whole
call.completed body to find out. Each is opt-in per endpoint — see
choosing events — and each carries the call, the analysis
it came from, and only the rows that raised it.
| Event | Fires when | The trigger block |
|---|---|---|
call.risk.high | The analysis holds at least one risk moment with severity: "high". | data.moments — the high-severity moments only, in call order. |
call.policy.breached | The analysis recorded at least one policy finding. | data.findings — every finding, each with severity, title and rationale. |
call.score.low | The call was scored and total_score is strictly below data.threshold. | data.scorecard — the same object call.completed carries — plus threshold. |
Once per analysis
A severity event fires once per analysis of the call, not once per call. data.analysis.version
says which analysis raised it; it starts at 1 and a re-analysis increments it. A re-analysis that
still finds the condition fires again with the new version, so a consumer that wants "first time
only" keys on the call reference and ignores later versions. A retried completion of the same
analysis never fires twice — delivery.idempotency_key is stable across retries exactly as it is
for call.completed.
call.completed itself is unaffected: it fires once per call, whatever the analysis version.
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.risk.high",
"occurred_at": "2026-09-05T09:24:11Z",
"delivery": { "idempotency_key": "3f9a1c22-...", "attempt": 1 },
"data": {
"call": { "reference": "cl_7Yx2bQ...", "status": "completed", "…": "…" },
"analysis": { "version": 1, "summary": "…", "risk_level": "high", "…": "…" },
"moments": [
{
"kind": "risk",
"risk_kind": "churn",
"severity": "high",
"title": "Customer threatened to cancel",
"rationale": "Said they would move to a competitor if the outage repeats.",
"suggestion": "Offer a credit and a named escalation contact.",
"start_ms": 10000,
"end_ms": 25000,
"segment_idx_from": 3,
"segment_idx_to": 4
}
]
}
}data.call and data.analysis are the objects call.completed publishes under the same names —
one shape, documented once. data.analysis is never null on a severity event: without an
analysis there is nothing to raise one from.
call.policy.breached replaces moments with findings:
"findings": [
{
"severity": "medium",
"title": "No callback window was offered",
"rationale": "The customer asked to be called back and no time was agreed."
}
]A finding carries no id and does not name the rule that raised it.
call.score.low replaces it with the scorecard and the line it was measured against:
"scorecard": { "template_name": "Support rubric", "total_score": 38, "criteria": [ "…" ] },
"threshold": 50threshold is sent with every event so you never need to know our configuration to read it.
What the payload contains
The disclosure on the overview applies in full. The titles and rationales on these events are model-generated text about the conversation, and they are as unredacted as the summary.