Overview
Registering endpoints and what gets delivered to them.
Call Lens delivers events by POSTing a signed JSON body to a URL you register. There is no
polling API — webhooks are how results reach you.
Registering an endpoint
curl -X POST https://api.calllens.io/api/v1/ingest/webhooks \
-H "Authorization: Bearer clk_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://your-app.example.com/hooks/calllens", "description": "Production"}'The URL must be https and must resolve to a public address. Anything else is refused with
422 webhook_url_refused. Registering a URL that is already registered returns
409 webhook_url_taken.
The response contains secret — the signing key for this endpoint. It is shown once. No
route returns it again. Store it before you make another request; if you lose it, your only
option is rotation.
The response also contains resolves_to, the addresses the URL resolved to at registration time,
so you can confirm we reached what you meant.
Events
| Event | When |
|---|---|
call.completed | A call finished processing. On by default. Full payload. |
call.risk.high | The analysis found a high-severity risk moment. Opt in. Details. |
call.policy.breached | The analysis recorded a policy finding. Opt in. Details. |
call.score.low | The call was scored below the alert threshold. Opt in. Details. |
call.failed | A call was quarantined — a required stage exhausted and there is no result. Opt in. Details. |
call.degraded | A call finished without an optional stage — it still has its transcript and metrics. Opt in. Details. |
webhook.test | You called POST /webhooks/{id}/test. |
Acknowledge any event you do not recognise with a 2xx. New event types arrive at the same URL
with the same signature scheme. A consumer that hard-fails on an unknown event will reject the
test probe and report itself broken.
Choosing events
Each endpoint carries an events list — the kinds it receives. Pass it to POST /webhooks when
you register, or change it later with PATCH /webhooks/{id} and {"events": [...]}. Omitted at
registration, it is ["call.completed"]; omitted on a patch, it is left alone. It can never be
empty — an endpoint subscribed to nothing would be indistinguishable from an organization with no
calls.
Delivery is decided per organization, not per API key. When a call reaches a terminal state,
Call Lens sends the kinds that state raises to every active endpoint your organization has
subscribed to them, with no reference to which key ingested the call. If your organization has any
active endpoint subscribed to call.completed, every call your organization ingests generates an
event — see correlation identifiers for what that means for keys that
were never asked for provider_call_id or idempotency_key.
A call reaches exactly one terminal state, so it raises call.completed (plus any severity events
its analysis triggers) or one of the two failure events, never
both at once. The single exception is a quarantined call we later retry successfully: it sends
call.failed and then, on recovery, call.completed.
Disabling an endpoint
PATCH /webhooks/{id} with {"active": false} stops delivery without destroying anything.
There is no delete route, and its absence is deliberate: deleting an endpoint would cascade to its delivery records, and those records are the evidence of what we sent you. Disabling is reversible; destroying the record is not.
The URL is not patchable either. Re-pointing an endpoint would move your analytics to a new destination while keeping the delivery history and signing secret, which reads in the record as continuity. Register a new endpoint instead.
What the payload contains
This payload is NOT redacted. It carries model-generated summaries of a real conversation and may contain personal data the speakers disclosed. Redaction (Task 7) is deferred post-MVP — ADR-0034, blocker B46 — and no masking is applied.
Context, so the risk is sized correctly rather than over- or under-stated: transcripts already leave unredacted to Azure and OpenAI on every call, so this does not move the platform's posture. What is new is the DESTINATION — the tenant's own data going to the tenant's own endpoint, at a URL the tenant chose.
Residency: the payload lands wherever that endpoint is. A tenant with an EU data requirement can defeat it with a webhook URL, and this system cannot detect that.
The severity events (call.risk.high, call.policy.breached, call.score.low) carry the titles and rationales of the moments or findings that raised them, which are model-generated text about the same conversation and are equally unredacted.
The same text is returned in the disclosure field of GET /webhooks and POST /webhooks.