Skip to main content

Voice WebSocket Protocol

Stream real-time voice call events over a persistent WebSocket connection. Use this protocol for live dashboards, call monitoring, real-time transcription displays, and custom call control interfaces.

Connection Setup

Endpoint

wss://api.useanima.sh/v1/events/ws

Authentication

Include your API key as a query parameter or in the first message after connection:
wss://api.useanima.sh/v1/events/ws?apiKey=ak_...
Or authenticate with an auth message after connecting:
{
  "type": "auth",
  "apiKey": "ak_..."
}
The server responds with an auth.success or auth.error message:
{
  "type": "auth.success",
  "connectionId": "conn_abc123",
  "serverTime": "2025-01-15T10:30:00.000Z"
}

SDK Connection

The SDKs handle connection, authentication, and reconnection automatically.
from anima import Anima

anima = Anima(api_key="ak_...")
stream = anima.events.connect()

for event in stream:
    print(f"{event['type']}: {event['data']}")
import { Anima } from "@anima-labs/sdk";

const anima = new Anima({ apiKey: "ak_..." });
const stream = anima.events.connect();

stream.on("event", (event) => {
  console.log(`${event.type}:`, event.data);
});

stream.on("error", (err) => {
  console.error("Stream error:", err);
});

Message Format

All messages follow a consistent envelope format:
{
  "type": "string",
  "timestamp": "ISO 8601 string",
  "data": { }
}
FieldTypeDescription
typestringMessage type identifier
timestampstringISO 8601 timestamp of the event
dataobjectEvent-specific payload

Message Types

call.started

Emitted when an outbound or inbound call is connected.
{
  "type": "call.started",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "callId": "call_abc123",
    "agentId": "ag_xyz789",
    "direction": "outbound",
    "from": "+14155551234",
    "to": "+14155555678",
    "voiceId": "voice_aria",
    "recordingEnabled": true
  }
}
FieldTypeDescription
callIdstringUnique call identifier
agentIdstringAgent handling the call
directionstring"outbound" or "inbound"
fromstringCaller phone number (E.164)
tostringCallee phone number (E.164)
voiceIdstringVoice model used for the call
recordingEnabledbooleanWhether recording is active

call.ringing

Emitted when the outbound call is ringing on the recipient’s end.
{
  "type": "call.ringing",
  "timestamp": "2025-01-15T10:30:01.000Z",
  "data": {
    "callId": "call_abc123"
  }
}

call.answered

Emitted when the recipient picks up.
{
  "type": "call.answered",
  "timestamp": "2025-01-15T10:30:05.000Z",
  "data": {
    "callId": "call_abc123",
    "answeredAt": "2025-01-15T10:30:05.000Z"
  }
}

call.transcription

Emitted in real-time as speech is transcribed. Segments may be partial (streaming) or final.
{
  "type": "call.transcription",
  "timestamp": "2025-01-15T10:30:10.000Z",
  "data": {
    "callId": "call_abc123",
    "segmentId": "seg_001",
    "speaker": "caller",
    "text": "Hi, I'd like to schedule an appointment.",
    "isFinal": true,
    "confidence": 0.97,
    "startTime": 5.2,
    "endTime": 8.1,
    "language": "en"
  }
}
FieldTypeDescription
segmentIdstringUnique segment identifier
speakerstring"agent" or "caller"
textstringTranscribed text
isFinalbooleanfalse for partial, true for finalized
confidencenumberTranscription confidence (0.0 - 1.0)
startTimenumberSeconds from call start
endTimenumberSeconds from call start
languagestringDetected language code

call.agent_response

Emitted when the agent generates a response that will be spoken.
{
  "type": "call.agent_response",
  "timestamp": "2025-01-15T10:30:11.000Z",
  "data": {
    "callId": "call_abc123",
    "text": "Of course! I can help you with that. What day works best for you?",
    "tokensUsed": 42,
    "latencyMs": 320
  }
}

call.dtmf

Emitted when the caller presses a key on their phone keypad (DTMF tone).
{
  "type": "call.dtmf",
  "timestamp": "2025-01-15T10:31:00.000Z",
  "data": {
    "callId": "call_abc123",
    "digit": "1",
    "durationMs": 120
  }
}
FieldTypeDescription
digitstringKey pressed: "0"-"9", "*", "#"
durationMsnumberHow long the key was held (milliseconds)

call.hold

Emitted when the call is placed on or taken off hold.
{
  "type": "call.hold",
  "timestamp": "2025-01-15T10:32:00.000Z",
  "data": {
    "callId": "call_abc123",
    "status": "on_hold"
  }
}
FieldTypeDescription
statusstring"on_hold" or "resumed"

call.transfer

Emitted when the call is transferred to another number or agent.
{
  "type": "call.transfer",
  "timestamp": "2025-01-15T10:33:00.000Z",
  "data": {
    "callId": "call_abc123",
    "transferTo": "+14155559999",
    "reason": "Customer requested human agent"
  }
}

call.sentiment

Emitted periodically with rolling sentiment analysis.
{
  "type": "call.sentiment",
  "timestamp": "2025-01-15T10:33:30.000Z",
  "data": {
    "callId": "call_abc123",
    "current": "positive",
    "score": 0.72,
    "trend": "improving"
  }
}
FieldTypeDescription
currentstring"positive", "neutral", or "negative"
scorenumberSentiment score (-1.0 to 1.0)
trendstring"improving", "stable", or "declining"

call.ended

Emitted when the call terminates for any reason.
{
  "type": "call.ended",
  "timestamp": "2025-01-15T10:35:00.000Z",
  "data": {
    "callId": "call_abc123",
    "reason": "completed",
    "durationSeconds": 300,
    "recordingUrl": "https://recordings.useanima.sh/call_abc123.wav",
    "transcriptAvailable": true,
    "summary": "Caller scheduled an appointment for January 20th at 2 PM."
  }
}
FieldTypeDescription
reasonstring"completed", "caller_hangup", "agent_hangup", "no_answer", "busy", "failed", "timeout"
durationSecondsnumberTotal call duration in seconds
recordingUrlstringSigned URL to the recording (if enabled)
transcriptAvailablebooleanWhether the full transcript is ready
summarystringAI-generated call summary

call.error

Emitted when an error occurs during the call.
{
  "type": "call.error",
  "timestamp": "2025-01-15T10:35:01.000Z",
  "data": {
    "callId": "call_abc123",
    "code": "voice_synthesis_timeout",
    "message": "Voice synthesis did not respond within 5 seconds",
    "recoverable": true
  }
}

Subscribing to Specific Calls

Filter events to a specific call or agent by sending a subscribe message:
{
  "type": "subscribe",
  "filters": {
    "callId": "call_abc123"
  }
}
{
  "type": "subscribe",
  "filters": {
    "agentId": "ag_xyz789",
    "eventTypes": ["call.transcription", "call.ended"]
  }
}
The server confirms with:
{
  "type": "subscribe.success",
  "subscriptionId": "sub_abc123",
  "filters": {
    "agentId": "ag_xyz789",
    "eventTypes": ["call.transcription", "call.ended"]
  }
}

Heartbeat / Ping-Pong

The server sends a ping message every 30 seconds. The client must respond with a pong within 10 seconds or the connection will be closed.
// Server sends
{
  "type": "ping",
  "timestamp": "2025-01-15T10:30:30.000Z"
}

// Client responds
{
  "type": "pong"
}
The SDKs handle ping-pong automatically. If you are implementing a raw WebSocket client, ensure you respond to every ping.

Error Handling

Connection Errors

CodeReasonAction
4001Invalid API keyCheck your API key and reconnect
4002Rate limit exceededBack off and retry after the Retry-After header
4003Connection limit reachedClose unused connections before opening new ones
4008Ping timeoutClient did not respond to ping in time; reconnect
4500Internal server errorRetry with exponential backoff

Reconnection Strategy

Use exponential backoff with jitter for automatic reconnection:
  1. First retry: 1 second
  2. Second retry: 2 seconds
  3. Third retry: 4 seconds
  4. Max backoff: 30 seconds
  5. Add random jitter of 0-1 seconds to each delay
// The SDK handles reconnection automatically
const stream = anima.events.connect({
  reconnect: true,            // default: true
  maxReconnectAttempts: 10,   // default: 10
  maxReconnectDelay: 30000,   // default: 30s
});

stream.on("reconnecting", (attempt) => {
  console.log(`Reconnecting (attempt ${attempt})...`);
});

stream.on("reconnected", () => {
  console.log("Reconnected successfully");
});
stream = anima.events.connect(
    reconnect=True,
    max_reconnect_attempts=10,
    max_reconnect_delay=30000,
)

Next Steps