WebSocket Protocol
The platform exposes a single WebSocket endpoint that handles both text and voice conversations between an end user and one agent. This is the protocol your client (browser, mobile app, ESP firmware, custom integration) speaks.
REST is documented separately — see the API Reference.
Endpoint
wss://api.<your-domain>/user/agents/{agent_id}/ws
One connection = one conversation with one agent.
Authentication
Pass the user's Cognito JWT one of two ways:
Authorizationheader —Authorization: Bearer <jwt>(preferred for native clients).tokenquery parameter —wss://.../ws?token=<jwt>(handy in browsers, where you can't set custom WS headers).
The server accepts the WebSocket first, then verifies the token. If verification fails, the server sends an error message and closes with code 1008.
There's no token refresh on the WebSocket itself — when your token expires, you reconnect with a fresh one.
Lifecycle at a glance
The first message on the socket must be a handshake. If it isn't, the server sends an error and closes the socket. See Errors & Lifecycle → Close codes for what to expect.
Message format
Every message — both directions — is a JSON envelope:
{
"type": "<message_type>",
"content_type": "<content_type>",
"content": <payload>,
"metadata": { ... }
}
Binary frames (no JSON) are used only for raw audio during voice chat.
See the Message Reference for every type and content_type.
What you can do over this socket
| Mode | Pages |
|---|---|
| Text chat | Text Chat — send user text, receive streamed assistant chunks. |
| Voice chat | Voice Chat — stream PCM/Opus audio, get audio back, with barge-in. |
| Tools | Tool Calls — tool_request / tool_response for client-executed tools. |
| Resume | Reconnect — pick up an existing conversation. |
| Errors | Errors & Lifecycle — error codes, close codes, TTL. |
Quick example (text)
const ws = new WebSocket(
`wss://api.example.com/user/agents/${agentId}/ws?token=${jwt}`
);
ws.onopen = () => {
// Step 1: send handshake first
ws.send(JSON.stringify({
type: "handshake",
content_type: "json",
content: { conversationType: "text" },
}));
};
ws.onmessage = (ev) => {
// Binary audio frames arrive as ArrayBuffer; JSON arrives as string
if (typeof ev.data !== "string") return;
const msg = JSON.parse(ev.data);
if (msg.type === "handshake_ack") {
// Step 2: send the first user message after the ack
ws.send(JSON.stringify({
type: "user",
content_type: "text",
content: "Hello!",
}));
} else if (msg.type === "assistant") {
process.stdout.write(msg.content); // streamed chunk
} else if (msg.type === "transaction_end") {
console.log("\n[turn done]");
}
};
Conventions
- JSON for control, binary for audio bytes. Never send raw audio inside a JSON message — use a binary frame between
audio_stream_startandaudio_stream_end. - Don't expect a single
assistantmessage — expect a stream. The agent emits multipleassistantchunks per turn, finished by atransaction_end. - Always handle
errorandclose. Even healthy sessions hit a TTL eventually. - One conversation per socket. Don't try to multiplex.
Read next
- Handshake — exact request/response shapes.
- Text Chat — text turn lifecycle.
- Voice Chat — audio formats, barge-in.
- Errors & Lifecycle — every error code you might see.
- Message Reference — every message type, every field.