How to build a voice bot on your own telephony with WebSocket streaming
Stream the live audio of a phone call over a WebSocket to your own speech-to-text, LLM, and text-to-speech, then send the synthesised reply back into the call. EnableX carries the telephony and the bidirectional audio pipe; you own the bot logic and the models.
Updated August 2026
Open the media stream in one request
On a connected call, send one request with your voice_id and WSS host, and EnableX opens a real-time bidirectional audio pipe to your STT, LLM, and TTS. Copy, paste, run.
curl -X PUT https://api.enablex.io/voice/v1/call/$voice_id/stream \
-u APP_ID:APP_KEY \
-H "Content-Type: application/json" \
-d '{ "wss_host": "wss://your-host:port" }'
const axios = require('axios');
const voiceId = 'YOUR_VOICE_ID';
const res = await axios.put(
`https://api.enablex.io/voice/v1/call/${voiceId}/stream`,
{ wss_host: 'wss://your-host:port' },
{ auth: { username: 'APP_ID', password: 'APP_KEY' } }
);
console.log(res.data);
import requests
from requests.auth import HTTPBasicAuth
voice_id = "YOUR_VOICE_ID"
res = requests.put(
f"https://api.enablex.io/voice/v1/call/{voice_id}/stream",
auth=HTTPBasicAuth("APP_ID", "APP_KEY"),
json={"wss_host": "wss://your-host:port"}
)
print(res.json())
What you are building
A voice bot on your own telephony has six moving parts: the phone call, the audio pipe, the audio format, your model stack, the turn-taking logic, and the deployment. EnableX gives you the call and the pipe. You bring the models and the conversation logic. The audio flows both ways over a single WebSocket: caller audio comes to your server, and your bot's synthesised audio goes back into the call.
Two facts shape the whole build. First, media streaming only works on a call that is already connected, so you always place or accept the call first. Second, the audio format on the wire is Base64-encoded ulaw (G.711 mu-law), 8000 Hz, mono, in both directions. If your STT or TTS speaks a different format, transcode at the edges.
Step 1:Place or receive a call
Get a connected call and hold on to its voice_id, which you use for every later request.
Outbound: send POST /voice/v1/call with to, from, and your event_url. EnableX posts webhook events (initiated, ringing, connected) to that URL.
Inbound: when a call arrives, EnableX posts an incomingcall webhook with the voice_id. Accept it with PUT /voice/v1/call/{voice_id}/accept.
Either way, wait for the connected webhook before you start streaming. Streaming on a call that is still ringing will fail.
Step 2:Open the media stream
On the connected call, call PUT /voice/v1/call/{voice_id}/stream with one field, wss_host, the full WSS address of your server including port. This is the request in the copy-run box above. EnableX then connects outbound to your server, so your WebSocket server must already be running, publicly reachable, and holding a valid TLS certificate.
A successful response returns state: "stream_started". If it returns stream_failed, check that the host is reachable, the port matches, the certificate is valid, and your firewall allows inbound connections from EnableX. To auto-start streaming the moment an outbound call connects, add a stream object under action_on_connect in the original POST /voice/v1/call instead of calling the stream endpoint separately.
Step 3:Handle bi-directional audio
Once the socket is open, EnableX sends events in order: connected, then start_media, then a continuous stream of media events. Keep the voice_id and stream_id from start_media; you need both to send audio back.
Each inbound media event carries one audio frame as a Base64 string in media.payload, with a seq number you can use to detect drops or reorder frames. Decode the Base64, then decode ulaw to PCM if your pipeline needs PCM.
To play audio back into the call, send your own media events over the same socket, using the matching voice_id and stream_id, an incrementing seq, and the same ulaw 8000 Hz mono format. EnableX injects it so the caller hears it immediately. Audio in any other format is dropped, so transcode your TTS output to ulaw before sending.
Step 4:Wire STT to LLM to TTS
This is your bot loop, and it runs on your server:
STT: stream the decoded caller audio into your speech-to-text engine (Deepgram, Google, Whisper, or the built-in EnableX speech recognition for Indian languages) for a live transcript.
LLM: pass the transcript, plus your system prompt, conversation state, and any function-calling or CRM lookups, to your model (GPT, Claude, Gemini, or an on-prem model) to decide the reply.
TTS: synthesise the reply text with your text-to-speech provider, transcode to ulaw 8000 Hz mono, and stream it back as media events.
Start TTS and playback as soon as the first tokens arrive rather than waiting for the full reply. Streaming each stage keeps mouth-to-ear latency down, which is what makes the bot feel natural.
Step 5:Handle barge-in and turn-taking
A natural bot lets the caller interrupt. When your STT or a voice-activity detector shows the caller has started speaking while the bot is talking, stop the bot audio at once: send a clear_media event with the stream_id and voice_id, and EnableX clears the audio it has queued. Then go back to listening and feed the new caller audio into STT.
For turn-taking, use end-of-speech detection from your STT to decide when the caller is done, rather than a fixed timer, and only then call the LLM. Handling barge-in and end-of-turn well is the difference between a bot that feels human and one that talks over people.
Step 6:Secure and deploy
Run your WebSocket server on a public WSS endpoint with a valid TLS certificate. Because EnableX connects to you, protect the socket: pass a signed, single-use, short-lived JWT as a query parameter in the wss_host value (for example wss://your-host:9091?token=...) and validate the signature, expiry, and single use during the upgrade handshake. Optionally check that the connecting IP is in EnableX's range.
To stop cleanly, send clear_media to flush queued audio, or call DELETE /voice/v1/call/{voice_id}/stream to end the stream while keeping the call live. When the call ends, EnableX sends stop_media on the socket and a disconnected webhook; close the socket and release resources. For managed voice AI without writing this loop yourself, use the EnableX AI Voice Agent.
Build with the EnableX Developer Docs
FAQ:Build a voice bot on WebSocket streaming
PUT /voice/v1/call/{voice_id}/stream with a JSON body containing your wss_host, the full WSS address of your server including port. EnableX then opens an outbound WebSocket connection to that host and begins streaming audio.connected webhook, then call the stream endpoint. To skip the extra call on outbound, add a stream object under action_on_connect in your POST /voice/v1/call so streaming auto-starts when the call is answered.media events over the same WebSocket, using the voice_id and stream_id from the start_media event, an incrementing seq, and ulaw 8000 Hz mono Base64 audio. EnableX decodes and injects it so the caller hears it immediately.clear_media event with the stream_id and voice_id. EnableX clears any queued bot audio so you can stop mid-sentence and start listening again.Start building your voice bot.
Get API keys and stream your first call. No credit card required.
Free trial credits · No credit card · API keys in 2 minutes