Realtime Voice
Build interactive voice conversation apps with realtime AI models.
Overview
Realtime voice models enable two-way audio conversations with AI. Unlike standard model runs that process a single input and return a result, realtime sessions maintain a persistent WebSocket connection where you stream microphone audio and receive AI speech in real time.
The flow is:
- Run the realtime model via POST /Run to get a
socketaccesstoken - Connect to the WebSocket and send
task_infowith your token - Wait for
task_stream_ready: the model is ready to receive audio - Stream microphone audio as binary frames
- Receive AI audio as binary frames and play them
- End the session with
task_session_end
Run Parameters
Each realtime model has its own set of parameters. Use POST /Tool/Detail to discover the exact parameters for a specific model. See Model Parameters for details on parameter types.
Available realtime conversation models include:
- openai/gpt-realtime-mini: GPT Mini Realtime Voice Assistant
- openai/gpt-realtime: GPT Realtime Voice Assistant
- elevenlabs/realtime-conversational-ai: ElevenLabs Conversational AI
Common parameters across realtime models typically include voice selection, system instructions, and audio format settings. Example run for an OpenAI realtime model:
curl -X POST "https://api.wiro.ai/v1/Run/openai/gpt-realtime-mini" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"voice": "marin",
"system_instructions": "You are a friendly assistant.",
"input_audio_format": "audio/pcm",
"output_audio_format": "audio/pcm",
"input_audio_rate": "24000",
"output_audio_rate": "24000"
}'
Important: Parameters vary per model. Always check the model's detail page or use /Tool/Detail to get the exact parameter list before integrating.
Connection & Registration
After running the task, connect to the WebSocket and register with task_info:
var ws = new WebSocket("wss://socket.wiro.ai/v1");
ws.onopen = function() {
ws.send(JSON.stringify({
type: "task_info",
tasktoken: "YOUR_SOCKET_ACCESS_TOKEN"
}));
};
Note: Both standard and realtime models use type: "task_info" with tasktoken to register on the WebSocket.
Realtime Events
During a realtime session, you'll receive these WebSocket events:
| Event | Direction | Description |
|---|---|---|
task_stream_ready |
server → client | Session is ready: start sending microphone audio |
task_stream_end |
server → client | AI finished speaking for this turn: you can speak again |
task_cost |
server → client | Cost update per turn: includes turnCost, cumulativeCost, and usage (raw cost breakdown from the model provider) |
task_output |
server → client | Transcript messages prefixed with TRANSCRIPT_USER: or TRANSCRIPT_AI: |
task_end |
server → client | The model process has exited. Post-processing follows. Wait for task_postprocess_end to close the connection. |
task_postprocess_end |
server → client | Post-processing complete: safe to close the WebSocket connection |
task_session_end |
client → server | Gracefully end the session |
task_session_interrupt |
client → server | Interrupt AI speech: stops playback and lets the user speak |
Audio Format
Both directions (microphone → server, server → client) use the same format:
| Property | Value |
|---|---|
| Format | PCM (raw, uncompressed) |
| Bit depth | 16-bit signed integer (Int16) |
| Sample rate | 24,000 Hz (24 kHz) |
| Channels | Mono (1 channel) |
| Byte order | Little-endian |
| Chunk size | 4,800 samples (200 ms) = 9,600 bytes |
Binary Frame Format
Every binary WebSocket frame (in both directions) is structured as:
[tasktoken]|[PCM audio data]
The pipe character | (0x7C) separates the token from the raw audio bytes.
Sending Microphone Audio
Capture microphone at 24 kHz using the Web Audio API with an AudioWorklet. Convert Float32 samples to Int16, prepend your task token, and send as a binary frame.
Key steps:
- Request microphone with
getUserMedia(enable echo cancellation and noise suppression) - Create an
AudioContextat 24,000 Hz sample rate - Use an AudioWorklet to buffer and convert samples to Int16
- Send each chunk as
tasktoken|pcm_databinary frame
Receiving AI Audio
AI responses arrive as binary WebSocket frames in the same PCM Int16 24 kHz format. To play them:
- Check if the message is a
Blob(binary) before parsing as JSON - Find the pipe
|separator and extract audio data after it - Convert Int16 → Float32 and create an
AudioBuffer - Schedule gapless playback using
AudioBufferSourceNode
Transcripts
Both user and AI speech are transcribed automatically. Transcripts arrive as task_output messages with a string prefix:
TRANSCRIPT_USER:for what the user saidTRANSCRIPT_AI:for what the AI said
// Example task_output message
{
"type": "task_output",
"message": "TRANSCRIPT_USER:What's the weather like today?"
}
{
"type": "task_output",
"message": "TRANSCRIPT_AI:I'd be happy to help, but I don't have access to real-time weather data."
}
Interrupting AI Speech
While the AI is speaking, you can interrupt it to take over the conversation. Send task_session_interrupt:
{
"type": "task_session_interrupt",
"tasktoken": "YOUR_SOCKET_ACCESS_TOKEN"
}
When the server receives this:
- The AI immediately stops generating audio
- A final
task_stream_endis sent for the interrupted turn - The session continues: the user can speak and the AI will respond to the next input
On the client side, stop audio playback immediately when the user triggers an interrupt. This gives instant feedback (AI voice cuts off) while the server processes the signal.
Tip: Some models support natural interruption: if the user starts speaking while the AI is talking, the model may stop on its own. Models whose provider does its own barge-in (the OpenAI realtime family and ElevenLabs) may handle interruption natively, while the explicit task_session_interrupt signal stops the current reply on Wiro-hosted realtime models.
Ending a Session
To gracefully end a realtime session, send task_session_end:
{
"type": "task_session_end",
"tasktoken": "YOUR_SOCKET_ACCESS_TOKEN"
}
After sending this, the server will process any remaining audio, send final cost/transcript events, and then emit task_postprocess_end. Wait for task_postprocess_end before closing the WebSocket.
Safety: If the client disconnects without sending task_session_end, the server automatically terminates the session to prevent the pipeline from running indefinitely (and the provider from continuing to charge). Always send task_session_end explicitly for a clean shutdown.
Insufficient balance: The session is charged turn by turn as it runs. When a charged turn uses up your available balance (the team wallet's balance when you use a team project's API key), the server stops the session right after that turn. That turn is charged in full, and so is any final usage reported while the session closes, so your balance can end slightly below zero. You still receive the final task_cost and task_end events.