WebSockets
Connect to an agent over WebSockets for audio or text sessions
WebSockets provide a lightweight connection to your agent for audio or text sessions. This is ideal for server-side integrations, headless clients, or any use case where you don't need a video avatar.
Connecting to an agent
Create a session, decode the token, open the socket
Audio protocol
Base64 PCM16 audio in both directions
Text sessions
Typed chat with modality set to text
Events and commands
Server events and client commands during a session
Barge-in (interruption)
Handle user interruptions in audio sessions
Connecting to an agent
Create a session
If you have an Omniagent, create a WebSocket session with a single call:
curl -X POST https://companion-api.napster.com/public/agents/agent_abc123/connections \
-H "X-Api-Key: $NAPSTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channelType": "websocket"
}'The agent's companion, voice, tools, knowledge, and provider settings are all inherited from the agent configuration. You can optionally pass externalClientId to enable cross-session memory.
The response returns a JSON object containing an encoded token and a connection object with the session id:
{
"token": "eyJhbGci...",
"connection": {
"id": "conn_abc123"
}
}Store the connection id on your backend to retrieve session details later, such as transcripts or duration.
Decode the token
The token is base64-encoded JSON. Decode it and read the url field — the WebSocket endpoint already has the token embedded as a ?token=… query parameter, so url is the only value you need to connect:
const decoded = JSON.parse(Buffer.from(token, "base64").toString());
const { url } = decoded;import base64
import json
decoded = json.loads(base64.b64decode(token))
url = decoded["url"]const decoded = JSON.parse(atob(token));
const { url } = decoded;The decoded object also includes token, connection, and expiresAt, but url is all you need to connect — the token is already embedded in it. Don't append ?token= yourself; a second one produces an invalid URL.
Open the WebSocket connection
Pass the decoded url straight to your WebSocket client — the token is already in the URL, so you don't set any auth header or query parameter yourself. The https URL is upgraded to a secure WebSocket connection automatically; you don't need to change the scheme:
import WebSocket from "ws";
const ws = new WebSocket(url);
ws.on("open", () => {
console.log("Connected to agent");
});
ws.on("message", (data) => {
const event = JSON.parse(data);
// Handle incoming events from the agent
});import websocket
import json
ws = websocket.WebSocketApp(
url,
on_open=lambda ws: print("Connected to agent"),
on_message=lambda ws, msg: print(json.loads(msg)),
)
ws.run_forever()const ws = new WebSocket(url);
ws.addEventListener("open", () => {
console.log("Connected to agent");
});
ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
// Handle incoming events from the agent
});Audio protocol
Audio is streamed as 16-bit integer PCM, 16 kHz, mono, base64-encoded in both directions.
Sending audio
Send audio to the agent using the send_audio message type:
{
"type": "send_audio",
"data": {
"data": "<base64-encoded PCM audio>"
}
}Receiving audio
The agent sends audio back via audio_received events:
{
"event": "audio_received",
"data": {
"data": "<base64-encoded PCM audio>"
}
}Text sessions
By default, a WebSocket session runs in audio mode — you stream microphone audio and receive the agent's voice, as described above. To run a text-only session instead — a typed text chat with no voice or audio (plain text in, plain text out, chatbot-style) — set modality to "text" when you create the connection:
curl -X POST https://companion-api.napster.com/public/agents/agent_abc123/connections \
-H "X-Api-Key: $NAPSTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channelType": "websocket",
"modality": "text"
}'modality accepts audio (the default) or text, and is also available on the per-session POST /public/ws-connections call. For a video avatar, use WebRTC instead.
Text mode is specifically text without audio. In an audio session you already receive the agent's text transcript through message_received events alongside the streamed audio — text mode simply drops the audio and keeps the text.
In a text session you don't use send_audio / audio_received. Send the user's message with the send_message command, and read the agent's reply from message_received events:
// send — the user's message
{ "type": "send_message", "data": { "role": "user", "text": "Hello", "trigger_response": true } }// receive — the agent's reply (note the fields are nested under data.message, not data)
{ "event": "message_received", "data": { "message": { "role": "assistant", "action": "delta", "content": "Hi" } } }See Client Commands for the full send_message reference and Server Events for the other events a session emits.
A text session has no speech, so speaking-related events never fire — there's no turn detection or barge-in, and no talk_state_changed. A text session emits only avatar_state_changed and message_received events.
Events and commands
The server sends events to your client throughout the session — state changes, speech activity, transcription, and response streaming. For the full event reference, message structure, and lifecycle details, see Server Events.
You can also send commands back to the server during a session to inject text messages or update configuration in real time. See Client Commands.
Barge-in (interruption)
Turn detection is always active in audio sessions. The server continuously listens for user speech during agent output and sends a speech_started event when the user interrupts. To handle barge-in correctly, your client should:
- Stop all queued audio playback immediately. When the server detects an interruption, it cancels the current response — but any audio already buffered on the client will keep playing unless you clear it.
- Keep sending microphone audio at all times. Do not mute the mic during agent playback. If the mic is muted, the server cannot detect that the user is speaking, and interruption becomes impossible.
- Enable echo cancellation. In browser environments, set
echoCancellation: trueingetUserMedia. Without it, the agent's own audio gets picked up by the microphone and creates a feedback loop where the agent constantly interrupts itself.
WebSocket sessions run in audio or text mode. If you need a video avatar, use WebRTC with the Web SDK instead.