Call Lens
Webhooks

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

EventWhen
call.completedA call finished processing. On by default. Full payload.
call.risk.highThe analysis found a high-severity risk moment. Opt in. Details.
call.policy.breachedThe analysis recorded a policy finding. Opt in. Details.
call.score.lowThe call was scored below the alert threshold. Opt in. Details.
call.failedA call was quarantined — a required stage exhausted and there is no result. Opt in. Details.
call.degradedA call finished without an optional stage — it still has its transcript and metrics. Opt in. Details.
webhook.testYou 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.

On this page