Connecting a real-time voice AI agent to WhatsApp usually sounds simple until you actually try to build it.
Within minutes, you run into the reality of Meta's telephony plumbing:
- Digging through Meta Business Manager for obscure IDs.
- Manually spinning up tunnels and pasting verification tokens.
- Enabling voice calling flags via Graph API endpoints.
- Handling WebRTC SDP offer/answer exchanges and SRTP audio.
- Diagnosing weird 5-second ringing delays before a call even connects.
I got tired of repeating this setup for every voice experiment, so I built an open-source CLI called WAVoice to collapse the entire setup into a single command.
Here is how to get a live WhatsApp AI agent answering calls in under 90 seconds.
What You Need
You only need 3 credentials:
-
Meta System User Access Token (with
whatsapp_business_messagingandwhatsapp_business_managementpermissions) - Meta App Secret (from your Meta App Basic Settings)
- Google Gemini API Key (or your own custom LLM key)
Step 1: Run the Setup Command
If you have uv installed, run:
uvx wavoice init
(Or via pip: pip install wavoice && wavoice init)
The wizard prompts for your 3 keys and automatically:
- Starts an ephemeral Cloudflare Quick Tunnel (public HTTPS URL, zero accounts needed).
- Auto-discovers your WABA ID and Phone Number ID from your access token.
- Programmatically turns on WhatsApp Voice Calling via Graph API.
- Configures webhook subscriptions.
- Launches the local WebRTC runtime.
✓ Found WhatsApp Business Account (WABA ID): 2349713195497707
✓ Found Phone Number: +1 555-188-2279 (Verified)
✓ WhatsApp Voice Calling is ENABLED
🚀 WhatsApp Voice Server is live!
Step 2: Test the Call
Open WhatsApp on your phone and tap the call button on your business number.
Your AI voice agent answers immediately. By default, it runs Gemini 2.0 Flash Live with sub-second (~300ms) speech-to-speech response times.
How It Works
WAVoice decouples the telephony networking from your AI logic:
- Telephony Layer (Automated): Cloudflare tunnel, Meta webhooks, WebRTC SDP handshake, and SRTP audio decryption.
-
AI Layer (Pluggable):
- Mode A (Out-of-the-box): Native Gemini Live S2S (~300ms latency, zero code required).
- Mode B (Custom Pipeline): Pipecat cascaded stack (STT -> LLM -> TTS).
Using Custom Models (Deepgram, OpenAI, Cartesia)
If you prefer a custom stack (Deepgram STT -> GPT-4o -> Cartesia TTS), WAVoice integrates natively with Pipecat:
import os
from wavoice import WhatsAppVoiceApp
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat.services.deepgram import DeepgramSTTService
from pipecat.services.openai import OpenAILLMService
from pipecat.services.cartesia import CartesiaTTSService
app = WhatsAppVoiceApp()
@app.on_call
async def handle_call(transport, call_info):
caller = getattr(call_info, "from_", "caller")
print(f"Call received from {caller}")
pipeline = Pipeline([
transport.input(), # Raw WebRTC audio in from WhatsApp
DeepgramSTTService(api_key=os.environ["DEEPGRAM_API_KEY"]),
OpenAILLMService(api_key=os.environ["OPENAI_API_KEY"], model="gpt-4o-mini"),
CartesiaTTSService(api_key=os.environ["CARTESIA_API_KEY"], voice_id="..."),
transport.output(), # Raw WebRTC audio out to WhatsApp
])
task = PipelineTask(pipeline=pipeline, params=PipelineParams(allow_interruptions=True))
runner = PipelineRunner()
await runner.run(task)
if __name__ == "__main__":
app.run()
Technical Note: Fixing the 5-Second Ringing Delay
During testing, callers were experiencing 5 to 7 seconds of ringing before the bot picked up.
Looking into aiortc and aioice, we found that macOS creates virtual tunnel interfaces (utun, common with VPNs and Docker). aioice was attempting STUN discovery on all interfaces sequentially, timing out for exactly 5,000ms on unreachable virtual interfaces before returning candidates.
By patching candidate gathering to query the kernel routing table for the primary routable interface (socket.connect(('8.8.8.8', 80))), candidate gathering dropped from 5,004ms down to ~15ms, eliminating the ringing delay completely.
Links & Code
Feedback and PRs are welcome!
Top comments (0)