Call Lens
Webhooks

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.

EventFires whenThe trigger block
call.risk.highThe analysis holds at least one risk moment with severity: "high".data.moments — the high-severity moments only, in call order.
call.policy.breachedThe analysis recorded at least one policy finding.data.findings — every finding, each with severity, title and rationale.
call.score.lowThe 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": 50

threshold 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.

On this page