<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Denegra</title>
    <description>The latest articles on DEV Community by Denegra (@denegraai).</description>
    <link>https://dev.to/denegraai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4225065%2Fded04fcd-2edd-47b6-aa14-333fa2fa9773.jpg</url>
      <title>DEV Community: Denegra</title>
      <link>https://dev.to/denegraai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/denegraai"/>
    <language>en</language>
    <item>
      <title>Let any AI agent answer WhatsApp voice notes in 10 minutes</title>
      <dc:creator>Denegra</dc:creator>
      <pubDate>Thu, 05 Nov 2026 21:17:56 +0000</pubDate>
      <link>https://dev.to/denegraai/let-any-ai-agent-answer-whatsapp-voice-notes-in-10-minutes-2mei</link>
      <guid>https://dev.to/denegraai/let-any-ai-agent-answer-whatsapp-voice-notes-in-10-minutes-2mei</guid>
      <description>&lt;p&gt;Many people on WhatsApp don't type. They hold the mic button and talk. If your AI agent only understands text, those users get nothing back.&lt;/p&gt;

&lt;p&gt;In this tutorial you'll connect an AI agent to WhatsApp so that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;typed&lt;/strong&gt; message gets a &lt;strong&gt;text&lt;/strong&gt; reply, and&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;voice note&lt;/strong&gt; gets a &lt;strong&gt;voice note&lt;/strong&gt; reply.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent can be anything that answers an HTTP POST: a LangGraph graph, a CrewAI flow, an n8n workflow or a ten-line FastAPI app. We'll use the last one so you can see every moving part, then swap in your real agent at the end.&lt;/p&gt;

&lt;p&gt;This is about voice notes, the recorded messages WhatsApp users send with the mic button, not live WhatsApp calls.&lt;/p&gt;

&lt;p&gt;The glue is &lt;a href="https://github.com/denegra-ai/agent-voice-bridge" rel="noopener noreferrer"&gt;agent-voice-bridge&lt;/a&gt;, a small open-source FastAPI gateway (Apache 2.0) we wrote for this. We looked for an existing project that combines all three: any agent over HTTP, both Meta and Twilio, and voice note in → voice note out. We didn't find one, so we built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WhatsApp user ──► Twilio ──► agent-voice-bridge ──► your agent
                                │    ▲                  │
                    speech-to-text  text-to-speech      │
                                │    │                  │
WhatsApp user ◄── Twilio ◄──────┴────┴──── reply_text ◄─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Twilio sends the incoming WhatsApp message to the bridge.&lt;/li&gt;
&lt;li&gt;If it's a voice note, the bridge downloads and transcribes it.&lt;/li&gt;
&lt;li&gt;The bridge POSTs one JSON body to your agent.&lt;/li&gt;
&lt;li&gt;Your agent answers with &lt;code&gt;{"reply_text": "..."}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The bridge sends the reply back: synthesised as a voice note if the user spoke, as text if they typed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your agent never sees a Twilio or Meta payload. The same agent also works on Meta's WhatsApp Cloud API without changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Docker and Python 3.11+&lt;/li&gt;
&lt;li&gt;A free &lt;a href="https://www.twilio.com" rel="noopener noreferrer"&gt;Twilio&lt;/a&gt; account (the WhatsApp Sandbox is free for testing)&lt;/li&gt;
&lt;li&gt;An OpenAI API key, or an Azure OpenAI resource with a Whisper and a TTS deployment&lt;/li&gt;
&lt;li&gt;A tunnel to give your laptop a public HTTPS URL: &lt;a href="https://ngrok.com" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; or &lt;code&gt;cloudflared&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;WhatsApp on your phone&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Write the agent (2 minutes)
&lt;/h2&gt;

&lt;p&gt;This is the whole agent. It just echoes what it heard, which is perfect for checking the pipes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# echo_agent.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reply_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You said (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi uvicorn
uvicorn echo_agent:app &lt;span class="nt"&gt;--port&lt;/span&gt; 9000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bridge will send it a body like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sender_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"whatsapp:+905551112233"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"voice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What time does the store open tomorrow?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"twilio"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sender_id&lt;/code&gt; is stable per person, so use it as your conversation or session key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Start a tunnel (1 minute)
&lt;/h2&gt;

&lt;p&gt;In a second terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ngrok http 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the &lt;code&gt;https://…ngrok-free.app&lt;/code&gt; URL. That's your public URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Join the Twilio WhatsApp Sandbox (2 minutes)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://console.twilio.com" rel="noopener noreferrer"&gt;Twilio Console&lt;/a&gt;, go to &lt;strong&gt;Messaging → Try it out → Send a WhatsApp message&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The page shows a sandbox number and a phrase like &lt;code&gt;join two-words&lt;/code&gt;. Send that phrase from your WhatsApp to the sandbox number.&lt;/li&gt;
&lt;li&gt;Note your &lt;strong&gt;Account SID&lt;/strong&gt; and &lt;strong&gt;Auth Token&lt;/strong&gt; from the Console home page.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 4: Configure and run the bridge (3 minutes)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/denegra-ai/agent-voice-bridge.git
&lt;span class="nb"&gt;cd &lt;/span&gt;agent-voice-bridge
python3 scripts/configure.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setup wizard asks only what your choices need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent URL:&lt;/strong&gt; &lt;code&gt;http://host.docker.internal:9000/agent&lt;/code&gt; (the bridge runs in Docker, your echo agent on your laptop)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public URL:&lt;/strong&gt; the ngrok URL from step 2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel:&lt;/strong&gt; Twilio, with your Account SID and Auth Token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speech providers:&lt;/strong&gt; OpenAI (or Azure OpenAI) for both speech-to-text and text-to-speech&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keys are typed hidden and checked against the real service before anything is saved. The wizard writes &lt;code&gt;.env&lt;/code&gt; and prints the webhook URL you need next.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
curl http://localhost:8000/health
&lt;span class="c"&gt;# {"status":"ok","version":"0.1.2","channels":["twilio"]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Point Twilio at the bridge (1 minute)
&lt;/h2&gt;

&lt;p&gt;On the same Twilio page, open &lt;strong&gt;Sandbox settings&lt;/strong&gt; and set &lt;strong&gt;When a message comes in&lt;/strong&gt; to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;your-ngrok-url&amp;gt;/webhook/twilio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Method &lt;code&gt;POST&lt;/code&gt;, then &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Talk to it
&lt;/h2&gt;

&lt;p&gt;Send a text to the sandbox number. You get:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You said (text): hello&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now hold the mic button and say something. A few seconds later you get a &lt;strong&gt;voice note&lt;/strong&gt; back that reads out "You said (voice): …" with your transcript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swap in your real agent
&lt;/h2&gt;

&lt;p&gt;Point &lt;code&gt;AGENT_WEBHOOK_URL&lt;/code&gt; at anything that accepts the JSON above and returns &lt;code&gt;reply_text&lt;/code&gt;. For example, a LangGraph graph with a checkpointer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;my_agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;  &lt;span class="c1"&gt;# your compiled LangGraph graph
&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ainvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])]},&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configurable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thread_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sender_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reply_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In n8n it's a &lt;strong&gt;Webhook&lt;/strong&gt; node (respond using "Respond to Webhook") and a &lt;strong&gt;Respond to Webhook&lt;/strong&gt; node that returns &lt;code&gt;{"reply_text": "..."}&lt;/code&gt; at the end of your workflow.&lt;/p&gt;

&lt;p&gt;If your agent needs a token, set &lt;code&gt;AGENT_AUTH_TOKEN&lt;/code&gt; and the bridge sends it as &lt;code&gt;Authorization: Bearer …&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things the bridge handles for you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts.&lt;/strong&gt; Twilio and Meta expect a fast webhook response. The bridge acknowledges immediately and does the slow work (transcription, your agent, speech synthesis) in the background, then replies through the provider's API. Your agent can take 20 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No double answers.&lt;/strong&gt; The agent call is retried only when your agent cannot have seen the message (connection errors, 429/502/503/504). A 500 or a read timeout is not retried.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security.&lt;/strong&gt; Twilio's &lt;code&gt;X-Twilio-Signature&lt;/code&gt; and Meta's &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; are checked. Message content is never logged. Generated audio sits at an unguessable URL and is deleted after an hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice note vs. audio file.&lt;/strong&gt; OpenAI and Azure OpenAI return Ogg/Opus, which WhatsApp shows as a real voice note. ElevenLabs returns MP3, which arrives as an audio attachment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallbacks.&lt;/strong&gt; If text-to-speech fails, the user still gets the answer as text.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Production:&lt;/strong&gt; the sandbox is for testing. Register your own WhatsApp sender in Twilio, or use Meta's WhatsApp Cloud API directly. Step-by-step guides are in the repo: &lt;a href="https://github.com/denegra-ai/agent-voice-bridge/blob/main/docs/twilio-setup.md" rel="noopener noreferrer"&gt;Twilio&lt;/a&gt;, &lt;a href="https://github.com/denegra-ai/agent-voice-bridge/blob/main/docs/meta-setup.md" rel="noopener noreferrer"&gt;Meta&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other providers:&lt;/strong&gt; set &lt;code&gt;STT_PROVIDER=deepgram&lt;/code&gt; or &lt;code&gt;TTS_PROVIDER=elevenlabs&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling:&lt;/strong&gt; webhook dedupe is in memory, so run one process per instance for now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A note on what's been tested: we've run the full voice round trip on a real phone through the Twilio sandbox, once with Azure OpenAI for speech and once with Deepgram (speech-to-text) and ElevenLabs (text-to-speech). The plain OpenAI option uses the same code as Azure OpenAI but hasn't had its own phone test yet. Meta's Cloud API path is verified up to the signed webhook and the agent call; we haven't yet done a full round trip on a phone through Meta. If you try it, we'd love to hear how it goes in the &lt;a href="https://github.com/denegra-ai/agent-voice-bridge/issues" rel="noopener noreferrer"&gt;issues&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The repo is here: &lt;strong&gt;&lt;a href="https://github.com/denegra-ai/agent-voice-bridge" rel="noopener noreferrer"&gt;https://github.com/denegra-ai/agent-voice-bridge&lt;/a&gt;&lt;/strong&gt;. Stars, issues and PRs are welcome.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;agent-voice-bridge is open source and backed by &lt;a href="https://denegra.ai" rel="noopener noreferrer"&gt;Denegra&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>whatsapp</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
