WhatsApp Bot
Overview
This cookbook builds a WhatsApp bot that replies to inbound text messages and voice notes on a WhatsApp number. It chains together three Sarvam AI APIs, Saaras STT to transcribe voice notes, a Sarvam chat model to generate a reply, and Bulbul TTS to optionally speak that reply back, behind a small Flask webhook.
What you’ll build:
- A Flask webhook that receives inbound WhatsApp messages, text or voice notes
- Automatic transcription of voice notes, with auto-detection of whichever language the sender uses
- A conversational reply generated by a Sarvam chat model
- An optional spoken voice-note reply, synthesized with Bulbul TTS
- Graceful fallback replies if a download or API call fails, so the sender never gets silence
Business Value
- Offer support on a channel most users already have open, no app install required
- Let users speak instead of type, useful for anyone more comfortable in a regional language than in written English
- Handle common questions in 11 languages without hiring per-language support staff
- Log every transcript for later analytics or QA review
Customer Support
Field & Agent Networks
Citizen Services
Answer common questions or triage issues directly inside the WhatsApp conversation customers already use, in their own language.
How It Works
Receive the message
Twilio POSTs every inbound WhatsApp message, text or voice note, to your Flask webhook as a standard form-encoded request.
Transcribe voice notes
If the message is a voice note, download it from Twilio (it arrives as OGG/Opus, which Sarvam’s STT REST API accepts directly, no transcoding needed) and transcribe it with Saaras v3, auto-detecting the sender’s language.
1. Prerequisites
- Python 3.9 or higher
- A Sarvam API key, sign up on the Sarvam AI Dashboard to get one
- A Twilio account, with an Account SID, an Auth Token, and access to the WhatsApp Sandbox (or a WhatsApp-enabled Sender for production)
- ngrok, to expose your local server during development
This cookbook uses the free Twilio Sandbox for WhatsApp for testing. Moving to production requires registering a WhatsApp Sender, which involves Meta Business verification and can take a few days. See Twilio’s WhatsApp docs when you’re ready to go live.
2. Install the SDK and Dependencies
3. Configure Twilio for WhatsApp
Set your keys as environment variables:
Loading these from environment variables, instead of hardcoding them, keeps credentials out of source control. The script below reads them via os.environ.
Twilio’s Sandbox is a shared number (+1 XXX XXX XXXX, shown on the console page below), used by every developer testing WhatsApp. Twilio only forwards messages from phone numbers that have explicitly opted in to your sandbox, so before writing any code, connect your own number:
- Open the Twilio Console and go to Messaging → Try it out → Send a WhatsApp message (or search for “WhatsApp sandbox” in the console search bar). This page shows the sandbox number and your account’s unique join code, for example
join happy-elephant. - From the WhatsApp app on your phone, send that exact
join <your-code>message to the sandbox number shown on that page (or scan the QR code Twilio shows, which pre-fills this message for you). - Twilio replies confirming you’re connected.
This opt-in is per phone number, not per account. Anyone else who wants to message your bot (a teammate, a demo user) must send the same join <code> message from their own WhatsApp number first, there’s no way around this for the Sandbox. The opt-in also expires 3 days after joining (or after the last message exchanged); if messages stop arriving, re-send the join code.
Once your webhook is running (Step 4 below) and exposed via ngrok, come back to the Sandbox settings page, find When a message comes in, and set it to your ngrok URL plus /whatsapp (for example, https://xxxx.ngrok-free.app/whatsapp), method HTTP POST.
4. The Full Bot Script
Everything below, imports, the Flask webhook, and the transcribe → chat → reply flow, lives in one script. Copy it into whatsapp_bot.py:
How it works
whatsapp_webhookis the single entry point Twilio calls for every inbound message.NumMediaandMediaContentType0tell you whether the message carries a voice note;Bodyholds typed text.- If a voice note is present, it’s downloaded with HTTP Basic Auth (Twilio media URLs require your Account SID and Auth Token) and handed straight to Saaras v3 with
language_code="unknown", so the bot works for a Hindi caller followed by a Tamil caller without any code changes. - The (transcribed or typed) text is sent to
sarvam-105balong with a system prompt that sets the bot’s persona and instructs it to keep replies short, since WhatsApp is a chat surface, not a document. - The
try/exceptaround the whole body exists because this endpoint is a system boundary, it calls two external services (Twilio’s media download, Sarvam’s API) over the network, either of which can fail independently of your code. Replying with a short apology keeps the conversation alive instead of leaving the sender with no response at all.
Voice notes longer than 30 seconds will fail on this synchronous STT endpoint. For longer audio, switch to the Batch STT API.
This script trusts every request to /whatsapp. Before going to production, validate the X-Twilio-Signature header on each request (using twilio.request_validator.RequestValidator) so requests can’t be spoofed as coming from Twilio.
Run it, then expose it with ngrok:
Send a text message, or a voice note, to your Twilio WhatsApp number from any phone. Your bot will transcribe and reply within a few seconds.
5. Reply with a Voice Note (Optional)
To have your bot speak its reply back instead of (or alongside) text, synthesize it with Bulbul TTS, serve the audio file from your own Flask app, and attach it to the TwiML response as media:
The TTS call has its own nested try/except, separate from the outer one, so a synthesis failure only drops the audio attachment, the sender still gets the text reply instead of the generic apology. request.host_url resolves to your current ngrok URL automatically, since ngrok forwards the original Host header.
WhatsApp only renders a native, waveform voice-note bubble for OGG/Opus audio. A .wav reply still plays back fine, just as a regular audio attachment rather than a voice-note bubble.
6. Example Conversation
Here’s what a typical exchange looks like once the bot is running, a Hindi voice note in, a text reply out:
Available Options
Supported Languages
Speaker Voices (Bulbul v3)
Male: Shubh (default), Aditya, Rahul, Rohan, Amit, Anand, Karun, and more
Female: Ritu, Priya, Neha, Pooja, Simran, Kavya, Ishita, and more
See Text-to-Speech for the full voice list.
Customization
Change the bot’s persona
SYSTEM_PROMPT in the script controls tone and behavior. Swap it for something domain-specific, for example a support bot that only answers questions about order status, and stays on-topic otherwise.
Reply in the sender’s own language
target_language_code in the TTS call is hardcoded to en-IN in the example above. Pair it with the language Saaras detected (transcript.language_code) so a Hindi voice note gets a Hindi voice-note reply, not an English one.
Move off the Sandbox
Before real users message your bot, register a WhatsApp Sender under Messaging → Senders → WhatsApp senders in the Twilio Console. This requires Meta Business verification and removes both the shared-number and 3-day opt-in limitations of the Sandbox.
Tips & Best Practices
- Language auto-detection: Use
language_code="unknown"in STT to auto-detect whichever language the sender types or speaks in, rather than hardcoding one. - Reply length: Keep system prompts explicit about reply length. WhatsApp message bodies are capped at 1600 characters by Twilio, and short replies also read better in a chat window.
- The 24-hour session window: WhatsApp only allows free-form replies within 24 hours of the user’s last message. Outside that window, you need a pre-approved template message, this is a WhatsApp Business Platform policy that neither Sarvam nor Twilio can bypass.
- Sandbox opt-in: If messages never reach your webhook, check that the sending number actually joined the Sandbox (Step 3), Twilio silently drops messages from numbers that haven’t opted in, and this is the most common reason nothing arrives.
- API key security: Load
SARVAM_API_KEY,TWILIO_ACCOUNT_SID, andTWILIO_AUTH_TOKENfrom environment variables rather than hardcoding them, especially outside local experimentation.
Error Handling
You may encounter these errors while using the underlying APIs:
- 403 Forbidden (
invalid_api_key_error), invalid Sarvam API key. Use a valid key from the Sarvam AI Dashboard. - 429 Too Many Requests (
insufficient_quota_error/rate_limit_exceeded_error), Sarvam credits exhausted or rate limit hit. Retry with exponential backoff. - 500 Internal Server Error (
internal_server_error), issue on Sarvam’s servers. Try again later; contact support if persistent. - Twilio error 63015, “recipient has not joined the Sandbox.” The sending number needs to send the
join <code>message first (or its 3-day opt-in has expired), see Step 3 above.
For the full Sarvam error-code table and SDK exception reference, see Errors & Troubleshooting.
Additional Resources
- Documentation: docs.sarvam.ai
- Related APIs: Speech-to-Text · Chat Completion · Text-to-Speech
- Twilio docs: Sandbox for WhatsApp · WhatsApp API Overview · Python Helper Library
- Community: Join the Discord Community
Final Notes
- Keep your API keys secure, prefer environment variables over hardcoding.
- The Sandbox is for development only, each recipient must opt in, and the connection expires after 3 days of inactivity.
- The webhook degrades gracefully: a failed transcription or chat call still gets the sender a reply, and a failed voice-note synthesis still gets them the text.
Keep Building! 🚀