Live Conversation WebSocket (Preview)

The Live Conversation WebSocket provides real-time updates about an active conversation’s state. It is a server-to-client only connection — the client does not send any messages after connecting.

Endpoint

wss://api.phonic.ai/v1/conversations/{conversation_id}/live/ws
ParameterLocationDescription
conversation_idPathThe ID of the conversation to observe

Authentication

Server-side clients can pass an API key in the Authorization: Bearer PHONIC_API_KEY header. Browser clients should have their backend create a short-lived session token, then pass it as a query parameter:

wss://api.phonic.ai/v1/conversations/{conversation_id}/live/ws?session_token=ph_session_...

Never expose a Phonic API key in browser code. Session tokens are organization-scoped and expire after their configured TTL.

Connection flow

  1. Connect to the WebSocket with valid credentials
  2. Receive a conversation-audio message containing the live HLS playlist URL
  3. Receive an initial conversation-updated message with the current conversation state
  4. Continue receiving conversation-updated messages as the conversation progresses
  5. Receive conversation-ended when the call finishes, while Phonic saves it
  6. Receive conversation-saved, after which the socket closes normally

Server-to-client messages

conversation-updated

Sent whenever the conversation state changes. Contains the full conversation object.

The conversation object has the same shape as the Conversation returned by the REST API, with is_live always set to true.

1{
2 "type": "conversation-updated",
3 "conversation": {
4 "id": "conv_12cf6e88-c254-4d3e-a149-ddf1bdd2254c",
5 "agent": {
6 "id": "agent_12cf6e88-c254-4d3e-a149-a7f1bdd22783",
7 "name": "support-agent",
8 "is_deleted": false
9 },
10 "org_id": "org_01K1WB8NT3EJJ4ZRKJ6BQZPZPS",
11 "project_id": "proj_ad0334f1-2487-4155-9df3-abd8129b29ad",
12 "external_id": "call-123",
13 "origin": "inbound",
14 "generate_welcome_message": false,
15 "is_welcome_message_interruptible": true,
16 "welcome_message": "Hello! How can I help you today?",
17 "template_variables": {},
18 "input_format": "mulaw_8000",
19 "output_format": "mulaw_8000",
20 "background_noise_level": 0,
21 "background_noise": null,
22 "intelligence_level": "default",
23 "text": "Hello! How can I help you today?\nHi, I need help with booking an appointment.\nOf course! I'd be happy to help.",
24 "duration_ms": 12500,
25 "summary": null,
26 "boosted_keywords": [],
27 "pronunciation_dictionary": [{"word": "Phuket", "pronunciation": "Poo-ket"}],
28 "min_words_to_interrupt": 1,
29 "default_language": "en",
30 "additional_languages": [],
31 "multilingual_mode": "request",
32 "push_to_talk": false,
33 "no_input_poke_sec": 30,
34 "generate_no_input_poke_text": false,
35 "no_input_poke_text": "Are you still there?",
36 "no_input_end_conversation_sec": 180,
37 "storage_key": null,
38 "started_at": "2025-07-30T23:45:00.000Z",
39 "ended_at": null,
40 "task_results": null,
41 "timezone": "America/Los_Angeles",
42 "system_prompt": "You are a helpful support assistant.",
43 "is_test": false,
44 "is_live": true,
45 "call_provider": "twilio_inbound_user",
46 "from_phone_number": "+15551234567",
47 "to_phone_number": "+15559876543",
48 "twilio_call_sid": null,
49 "created_by": null,
50 "vad_min_silence_duration_ms": null,
51 "assistant_end_conversation_signal_at": null,
52 "retranscribed_text": null,
53 "end_reason": null,
54 "vad_info": { "events": [] },
55 "annotations": {},
56 "items": [
57 {
58 "id": "conv_12cf6e88-c254-4d3e-a149-ddf1bdd2254c-0",
59 "item_idx": 0,
60 "role": "assistant",
61 "text": "Hello! How can I help you today?",
62 "retranscribed_text": null,
63 "duration_ms": 1800,
64 "audio_speed": 1,
65 "voice": null,
66 "tool_call_ids": [],
67 "transcript_corrections": [],
68 "naturalness_report": null,
69 "hallucination_report": null,
70 "started_at": "2025-07-30T23:45:00.500Z",
71 "assistant_chose_not_to_respond": false,
72 "timings": {
73 "phonic_api_output_to_modal_input": 120,
74 "model_generation_time": 450,
75 "phonic_api_output_to_first_audio_chunk": 570
76 }
77 },
78 {
79 "id": "conv_12cf6e88-c254-4d3e-a149-ddf1bdd2254c-1",
80 "item_idx": 1,
81 "role": "user",
82 "text": "Hi, I need help with booking an appointment.",
83 "retranscribed_text": null,
84 "duration_ms": 2500,
85 "audio_speed": null,
86 "voice": null,
87 "tool_call_ids": [],
88 "transcript_corrections": [],
89 "naturalness_report": null,
90 "hallucination_report": null,
91 "started_at": "2025-07-30T23:45:03.000Z",
92 "assistant_chose_not_to_respond": false
93 },
94 {
95 "id": "conv_12cf6e88-c254-4d3e-a149-ddf1bdd2254c-2",
96 "item_idx": 2,
97 "role": "assistant",
98 "text": "Of course! I'd be happy to help.",
99 "retranscribed_text": null,
100 "duration_ms": 2100,
101 "audio_speed": 1,
102 "voice": null,
103 "tool_call_ids": [],
104 "transcript_corrections": [],
105 "naturalness_report": null,
106 "hallucination_report": null,
107 "started_at": "2025-07-30T23:45:06.200Z",
108 "assistant_chose_not_to_respond": false,
109 "timings": {
110 "phonic_api_output_to_modal_input": 95,
111 "model_generation_time": 380,
112 "phonic_api_output_to_first_audio_chunk": 475
113 }
114 }
115 ]
116 }
117}

conversation-audio

Sent after authentication and authorization succeed. The URL is stable and contains no credentials.

1{
2 "type": "conversation-audio",
3 "url": "https://api.phonic.ai/v1/conversations/conv_12cf6e88-c254-4d3e-a149-ddf1bdd2254c/live/audio.m3u8"
4}

Server-side clients fetch the playlist and every segment it references with the same Authorization: Bearer ... header used for the WebSocket. Browser clients add the same session_token query parameter to every playlist refresh and segment request. The playlist is a growing HLS event playlist, typically a few seconds behind the call. Caller audio is mixed into the left channel and assistant audio into the right channel.

The playlist and segment endpoints are live-only. After conversation-saved, use the regular Conversation API response and its final audio_url.

conversation-ended

Sent when the call has ended and Phonic has started saving it. Keep the WebSocket open until conversation-saved so you know when the saved conversation is available.

1{
2 "type": "conversation-ended"
3}

conversation-saved

Sent after the conversation has been stored. Phonic then closes the WebSocket with close code 1000.

1{
2 "type": "conversation-saved"
3}

error

Sent on authentication failure, authorization failure, or other errors. The error may appear in error.message or the top-level message field.

1{
2 "type": "error",
3 "error": {
4 "message": "Invalid token"
5 }
6}
1{
2 "type": "error",
3 "message": "Unauthorized access to conversation"
4}

Close codes

CodeDescription
1000Normal closure after the conversation is saved
1008Authentication or authorization error

Example usage

1import WebSocket from "ws";
2
3const apiKey = "ph_...";
4const conversationId = "conv_12cf6e88-c254-4d3e-a149-ddf1bdd2254c";
5
6const ws = new WebSocket(
7 `wss://api.phonic.ai/v1/conversations/${conversationId}/live/ws`,
8 { headers: { Authorization: `Bearer ${apiKey}` } }
9);
10
11ws.onmessage = (event) => {
12 const data = JSON.parse(event.data);
13
14 switch (data.type) {
15 case "conversation-audio":
16 console.log("Live audio playlist:", data.url);
17 break;
18
19 case "conversation-updated":
20 console.log(`Conversation updated. (${data.conversation.items.length} turns)`)
21 break;
22
23 case "conversation-ended":
24 console.log("Conversation ended; waiting for storage");
25 break;
26
27 case "conversation-saved":
28 console.log("Conversation saved");
29 ws.close();
30 break;
31
32 case "error":
33 console.error("Error:", data.error?.message ?? data.message);
34 ws.close();
35 break;
36 }
37};

Playing live audio in a browser

Browsers generally need an HLS client such as HLS.js because a native <audio src="..."> request cannot add a session token to playlist refreshes and segment requests.

$npm install hls.js
1import Hls from "hls.js";
2
3const audio = document.querySelector("audio");
4let sessionToken = await getSessionTokenFromYourBackend();
5const liveWebSocketUrl = new URL(
6 `wss://api.phonic.ai/v1/conversations/${conversationId}/live/ws`
7);
8liveWebSocketUrl.searchParams.set("session_token", sessionToken);
9const liveWebSocket = new WebSocket(liveWebSocketUrl);
10
11if (!audio || !Hls.isSupported()) {
12 throw new Error("HLS.js playback is not supported in this browser");
13}
14
15const hls = new Hls({
16 fetchSetup(context, initParams) {
17 const authenticatedUrl = new URL(context.url);
18 authenticatedUrl.searchParams.set("session_token", sessionToken);
19 return new Request(authenticatedUrl, initParams);
20 },
21});
22
23liveWebSocket.addEventListener("message", (event) => {
24 const message = JSON.parse(event.data);
25
26 if (message.type === "conversation-audio") {
27 hls.loadSource(message.url);
28 hls.attachMedia(audio);
29 }
30});

The session token must remain valid for the WebSocket, playlist, and segment requests. For longer conversations, renew it through your backend before it expires and update the token used by your HLS request hook.

Do not embed a Phonic API key in browser code. Create the short-lived session token on a trusted backend. For an API-key integration, consume or proxy the stream from your trusted backend.

Live audio errors

Playlist and segment requests can return:

StatusMeaning
401Authentication is missing or invalid
404The conversation is unavailable, belongs to another organization, or is no longer live
410Audio recordings for the conversation were deleted