Overview
What you’ll build: An outbound calling system with real-time AI conversations powered by OpenAI (STT → LLM → TTS), automatic call recording, and bidirectional audio streaming. Three moving parts do all the work:Call flow
answer_url from PUBLIC_URL./ws, then a single start event carrying the IDs and the negotiated audio format.parse_vobiz_start() reads this before the transport is built. mediaFormat is authoritative — prefer it over your own contentType.media events.auto_hang_up=True the serializer also issues a REST hangup, so a bot-initiated end tears the call down cleanly./answer URL directly. See Receiving inbound calls.Features
AI Voice Conversations
Outbound Calling
Automatic Recording
Real-time Streaming
Prerequisites
Version compatibility
This integration targets the Pipecat 1.x API. Versions are pinned inrequirements.txt:
FrameSerializer.setup() took a StartFrame before 1.3 and a FrameProcessorSetup after).pipecat.pipeline.task in favour of pipecat.pipeline.worker (PipelineTask → PipelineWorker). The old import path still works for all of 1.x and is what this repo uses, but it is scheduled for removal in Pipecat 2.0 — which is why the pin is <2.Installation
Clone the repository
Install dependencies
Configure environment
env.example file. Copy it and fill in your values:Usage
Start the server
http://0.0.0.0:7860.Start ngrok
https://abc123.ngrok-free.app).Make a call
- Server's /start helper (easiest)
- Direct Vobiz API
POST /start on the local server. It wraps the Vobiz Call API and auto-fills answer_url from your PUBLIC_URL, plus uses VOBIZ_PHONE_NUMBER as from when set.- Phone rings at the
phone_numberyou passed - When answered, Vobiz requests XML from your server’s
/answerendpoint - Server returns
<Record>+<Stream>pointing atwss://…/ws - Vobiz opens the WebSocket and sends a
startevent, thenmediaframes - AI assistant speaks and listens (STT → LLM → TTS)
- On hangup, Vobiz posts the recording URL to
/recording-ready, which downloads it torecordings/
How the flow works
Everything above is driven by one XML document and one WebSocket protocol. This section is what you need if you are adapting the repo rather than running it as-is.The answer XML
/answer returns this (with {PUBLIC_URL} and the wire format substituted in):
The WebSocket protocol
Vobiz speaks JSON text frames.VobizFrameSerializer handles all of this for you; the shapes are here so you can debug what you see on the wire.
start — sent once, immediately after the upgrade
start — sent once, immediately after the upgrade
bot.py reads this with parse_vobiz_start() before building the transport, because it carries both the IDs needed for REST hangup and the negotiated audio format.mediaFormat is authoritative. It reflects what the media server actually negotiated. If it differs from the contentType you asked for in your <Stream> XML, trust this event — the XML is a request, the start event is the agreement. The serializer adopts these values automatically and logs a warning on mismatch.media — the audio frames, both directions
media — the audio frames, both directions
stop — the call is over
stop — the call is over
auto_hang_up=True the serializer also issues a REST hangup using call_id + your auth credentials, so a bot-initiated end tears the call down properly rather than leaving it hanging.Sample rates
The Pipecat pipeline
bot.py builds a standard cascaded pipeline. Two details are specific to Pipecat 1.x and telephony:
transport.output() sits before context_aggregator.assistant() so what the bot actually said is what gets recorded into context:
Receiving inbound calls
Configure your Vobiz number to handle incoming calls with your Pipecat agent.Open Applications

Create an application

Configure URLs
https://.../answer) and select POST method. You can use the same URL for Hangup or leave it blank.
Assign phone number

Quick reference
Server endpoints
Project files
VobizFrameSerializer and parse_vobiz_start are not in this repo — they ship in the separate pipecat-vobiz package, which installs into the pipecat.serializers namespace. That is why bot.py imports them from pipecat.serializers.vobiz rather than from a local file.Customizing the bot
Editbot.py to customize your AI assistant:
Troubleshooting
Import errors right after pip install
Import errors right after pip install
pipecat-ai 1.x requires 3.11+, and on 3.10 pip installs a 0.0.x release instead of failing. Run python --version, then pip show pipecat-ai — if the version starts with 0.0., recreate your virtualenv on 3.11+.The bot never replies — it hears nothing
The bot never replies — it hears nothing
vad_analyzer belongs on LLMUserAggregatorParams, not on FastAPIWebsocketParams. The transport no longer has that field, and Pydantic discards unknown fields silently, so the mistake produces no error at all — just a bot that never detects the end of a turn.The caller hears static or noise instead of speech
The caller hears static or noise instead of speech
add_wav_header=False on FastAPIWebsocketParams. A WAV header on every frame is played as audio by the media server.start event arrives, then no media frames
start event arrives, then no media frames
audioTrack="both" together with bidirectional="true". That combination is not supported and fails silently. Use audioTrack="inbound".Media frames arrive but STT produces nothing (L16 only)
Media frames arrive but STT produces nothing (L16 only)
VOBIZ_L16_ENDIAN=le. The docs and RFC 2586 specify big-endian, but some accounts transport L16 little-endian. Using the default audio/x-mulaw avoids the problem entirely.The WebSocket never opens at all
The WebSocket never opens at all
24000 (not reliably supported — use 8000 or 16000), or PUBLIC_URL is stale. After restarting ngrok the URL changes; update PUBLIC_URL and restart the server, since the answer XML is built from it.Vobiz cannot reach /answer
Vobiz cannot reach /answer
PUBLIC_URL is unset the server falls back to the request Host header, which is localhost in local runs — Vobiz cannot route to that. The server prints a warning when this happens. Set PUBLIC_URL to your public HTTPS URL.The downloaded recording is named .mp3 but will not play as one
The downloaded recording is named .mp3 but will not play as one
fileFormat="wav", so Vobiz serves a .wav URL — but download_recording.py unconditionally appends .mp3 to the saved filename. The bytes are a RIFF/WAVE container (8 kHz, 16-bit, stereo); only the extension is wrong. Rename it to .wav, or change the fileFormat in the <Record> element to match the extension you want.Next steps
- Customize your AI assistant’s personality in
bot.py - Deploy to production (AWS/GCP/Heroku) instead of ngrok
- Add custom business logic and integrations
Resources
Vobiz Documentation External ResourcesBuild it with an AI agent
Clone, configure, and run the Vobiz-X-Pipecat repo - your first AI voice agent in ~5 minutes.