Skip to main content
Mint with kupe.realtime.sessions.create, then kupe.realtime.connect(session). Audio is PCM16 mono 24 kHz. Model kupe-realtime. Web-only — no telephony minutes. Identify the agent with name or agent_id. A new name creates the agent with prompt, greeting, voice, and tools/mcp. Pass voice (sanitized name) or voice_id — either one (copy the id from the voice library).

Terminal

Node 18+ (ESM). From a folder where kupe-sdk is installed:
File form (realtime.mjs):

Events you will see

sendText / send_text send conversation.item.create then response.create. Mic path: rt.appendAudio(pcm16) at 24 kHz.

Echo: speakers vs headset

If you play the agent’s audio through open speakers next to the mic, the mic records the agent and appendAudio sends its own voice back as user speech. The agent then answers itself, and its greeting shows up in the transcript as a user turn. Pass echoSuppression: "half_duplex" to mute the mic while the agent is still speaking:
appendAudio returns true when the chunk was sent and false when it was muted; rt.suppressedChunks counts the muted chunks, and rt.agentIsSpeaking exposes the gate. Pass { force: true } to bypass it for one chunk. Tune the hold after playback ends with echoTailMs (default 250). A muted chunk is still sent, as silence. The server runs streaming VAD and STT over a continuous audio stream, so sending nothing at all would stall turn detection and the agent would stop hearing you even after it stopped talking.
In the browser you normally want the default "none": request the mic with getUserMedia({ audio: { echoCancellation: true } }) and the browser cancels the echo for you, keeping barge-in working.
Full event tables: Realtime WebSocket.