Events & streaming

Watch calls as they happen, and resume exactly where you left off.

Two streams, both server-sent events over plain HTTP with the same bearer token. The organization stream carries lifecycle events for the whole tenant; the per-call stream carries everything about one call, including the events that are far too frequent to push over HTTP: transcript segments and live voice updates. Every event carries an id, and reconnecting with Last-Event-ID replays exactly what was missed before the live feed continues, so a dropped connection costs nothing. The same events feed outbound webhooks, so a stream and a webhook tell you the same things in the same words.

An event's id is the resume point for the stream it came from, and only that stream. Each stream numbers its own entries, so one event seen on the call stream and on the organization stream carries a different id on each, and a webhook delivery's id is different again. Resume a stream with the last id it gave you. To deduplicate webhook deliveries, use X-Vocalis-Event-Id (equal to the body's id), which stays the same on every retry of one delivery. Do not join events across surfaces by id; use type, callId and the ids inside data.

Organization stream#

Every non-stream-only event in the organization. Open it once and leave it open; heartbeats keep it alive through idle proxies.

GET/api/v1/events

Query parameters

typesstring
Comma-separated event types to narrow to. An unknown name is rejected.
Examplecall.started,call.ended
lastEventIdstring
Resume point. The `Last-Event-ID` header does the same thing.

Responses

An event stream (text/event-stream).

curl -X GET "https://voice.evryhealth.com/api/v1/events" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"event": "call.ended",
"id": "1789525082142-0",
"data": {
"type": "call.ended",
"organizationId": "7380…",
"callId": "5f1c…",
"occurredAt": "2026-09-15T14:07:44.101Z",
"data": {
"status": "completed",
"durationSeconds": 184,
"engine": "realtime"
}
}
}

Call stream#

Everything happening on one call: lifecycle, transcript segments as they are finalized (pipeline and speech-to-speech calls, and calls taken by people in a call center; a realtime-engine call's transcript is read from the call once it ends), who the analyzer thinks is speaking, and how the call sounds. The stream stays useful after the call ends. The after-call passes publish here too, so final voice insights arrive minutes later on the same connection.

GET/api/v1/calls/{callId}/events

Path parameters

callIdstringrequired
Platform call id.

Query parameters

typesstring
Comma-separated event types.
lastEventIdstring
Resume point.

Responses

An event stream (text/event-stream).

curl -X GET "https://voice.evryhealth.com/api/v1/calls/{callId}/events" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"event": "intelligence.speaker.identified",
"id": "1789525083001-0",
"data": {
"type": "intelligence.speaker.identified",
"callId": "5f1c…",
"data": {
"status": "returning",
"name": "Maria",
"timesSpokenBefore": 3,
"source": "live"
}
}
}

Event catalog#

Every event type, what it means, and whether it can be delivered by webhook. Served from the same constant the publishers and the delivery worker read, so it cannot drift from what actually fires.

GET/api/v1/events/types

Responses

The catalog.

curl -X GET "https://voice.evryhealth.com/api/v1/events/types" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"data": {
"events": [
{
"type": "call.started",
"description": "The call connected.",
"delivery": [
"stream",
"webhook"
]
},
{
"type": "call.transcript.updated",
"description": "A transcript segment was finalized.",
"delivery": [
"stream"
]
}
]
},
"meta": {
"requestId": "req_…"
}
}