Deploy agents, teams, or workflows as WhatsApp bots via the WhatsApp Interface using Agno.
The WhatsApp interface lets you deploy agents, teams, or workflows as WhatsApp bots that handle text, images, video, audio, and documents.
Setup
Follow the WhatsApp setup guide to create a Meta App, configure the WhatsApp Business API, and set up webhooks.
Install dependencies: uv pip install 'agno[os]' openai anthropic ddgs
Save one example tab using its displayed filename and run it with Python (for example, python basic.py). Set OPENAI_API_KEY for the Agent or Workflow tab, or ANTHROPIC_API_KEY for the Team tab, in the server terminal.
Required configuration:
WHATSAPP_ACCESS_TOKEN(from Meta App Dashboard > WhatsApp > API Setup)WHATSAPP_PHONE_NUMBER_ID(from WhatsApp > API Setup)WHATSAPP_VERIFY_TOKEN(any string you choose for webhook verification)WHATSAPP_APP_SECRET(from App Settings > Basic, used to validate incoming webhook signatures)- Webhook URL set to
{prefix}/webhook(use ngrok for local development)
Example Usage
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIChat
from agno.os.app import AgentOS
from agno.os.interfaces.whatsapp import Whatsapp
agent_db = SqliteDb(db_file="tmp/persistent_memory.db")
basic_agent = Agent(
name="Basic Agent",
model=OpenAIChat(id="gpt-5.4-mini"),
db=agent_db,
add_history_to_context=True,
num_history_runs=3,
add_datetime_to_context=True,
markdown=True,
)
agent_os = AgentOS(
agents=[basic_agent],
interfaces=[Whatsapp(agent=basic_agent)],
)
app = agent_os.get_app()
if __name__ == "__main__":
agent_os.serve(app="basic:app", reload=True)from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.anthropic import Claude
from agno.os.app import AgentOS
from agno.os.interfaces.whatsapp import Whatsapp
from agno.team import Team
from agno.tools.websearch import WebSearchTools
model = Claude(id="claude-sonnet-4-6")
team_db = SqliteDb(db_file="tmp/support_team.db")
researcher = Agent(
name="Researcher",
role="Find accurate, up-to-date information on the web",
model=model,
tools=[WebSearchTools()],
)
writer = Agent(
name="Writer",
role="Turn research into clear, friendly WhatsApp replies",
model=model,
)
support_team = Team(
name="Support Team",
model=model,
members=[researcher, writer],
db=team_db,
add_history_to_context=True,
num_history_runs=3,
markdown=True,
)
agent_os = AgentOS(
teams=[support_team],
interfaces=[Whatsapp(team=support_team)],
)
app = agent_os.get_app()
if __name__ == "__main__":
agent_os.serve(app="support_team:app", reload=True)from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.os.app import AgentOS
from agno.os.interfaces.whatsapp import Whatsapp
from agno.tools.websearch import WebSearchTools
from agno.workflow import Parallel, Step, Workflow
analyst = Agent(
name="Visual Analyst",
model=OpenAIChat(id="gpt-5.4-mini"),
)
researcher = Agent(
name="Web Researcher",
model=OpenAIChat(id="gpt-5.4-mini"),
tools=[WebSearchTools()],
)
creative_workflow = Workflow(
name="Creative Pipeline",
steps=[
Parallel(
Step(agent=analyst, name="Analyze"),
Step(agent=researcher, name="Research"),
),
],
)
agent_os = AgentOS(
workflows=[creative_workflow],
interfaces=[Whatsapp(workflow=creative_workflow)],
)
app = agent_os.get_app()
if __name__ == "__main__":
agent_os.serve(app="multimodal_workflow:app", reload=True)See more examples including media agents, image generation, reasoning, and multiple bots.
Sessions
Each WhatsApp user gets a single session scoped to their phone number.
Session format: wa:{entity_id}:{user_id}. For example, an agent named "Basic Agent" talking to user +1234567890 produces wa:basic-agent:1234567890 (AgentOS generates the ID from the name).
Users can send /new to start a fresh session. The old session is preserved in the database, and a new session ID is created with a random suffix. This requires a db on the agent, team, or workflow.
Media
Users can send images, video, audio, and documents to the bot. Media is downloaded from Meta's servers and passed to the agent automatically. Agents can send media back in their responses.
See the media agent example for a complete multimodal agent, and the reference page for supported message types.
WhatsAppTools
WhatsAppTools lets your agents send interactive messages: reply buttons, list menus, images, documents, locations, and reactions.
from agno.tools.whatsapp import WhatsAppTools
agent = Agent(
tools=[WhatsAppTools(
enable_send_reply_buttons=True,
enable_send_list_message=True,
enable_send_location=True,
)],
)See the interactive concierge example for a full agent using all interactive features.
For parameters and methods, see the WhatsAppTools reference.
Multi-Instance
A route prefix separates HTTP paths, but it does not provide a separate signing secret or session namespace. Every Whatsapp interface in a process reads the same WHATSAPP_APP_SECRET; the constructor has no app_secret argument.
For bots belonging to independent Meta Apps with different signing secrets, use a separate process or deployment per App. Configure each process with that App's access token, phone number ID, verification token, and App Secret. Point each App's callback at its corresponding deployment.
For example, adapt the Agent tab in one deployment:
basic_agent.id = "basic-whatsapp-agent"
agent_os = AgentOS(
agents=[basic_agent],
interfaces=[Whatsapp(agent=basic_agent, prefix="/basic")],
)
app = agent_os.get_app()Its callback path is /basic/webhook. Another bot needs its own configured deployment and entity ID. Distinct entity IDs also keep persisted sessions separate when a database is shared. Changing only a route prefix does not isolate conversations.
Phone Number Encryption
Enable with enable_encryption=True to encrypt phone numbers before storing them as user_id. This transforms interface-generated identity fields, not every value stored by the application. send_user_number_to_context=True injects the raw number into dependencies, and messages or tools can also include it. Leave that flag disabled if the raw number should not enter model context.
Whatsapp(agent=my_agent, enable_encryption=True)
# Set WHATSAPP_ENCRYPTION_KEY env var (64 hex chars = 32 bytes)Phone numbers are encrypted with AES-256-GCM using a deterministic nonce, so the same phone always maps to the same user_id. Requires the cryptography package.
Security
Every incoming webhook is verified using HMAC-SHA256 signature validation:
- The
X-Hub-Signature-256header is extracted - A signature is computed:
HMAC-SHA256(app_secret, request_body) - The computed signature is compared using constant-time comparison (
hmac.compare_digest)
If WHATSAPP_APP_SECRET is not set, the server returns a 500 error unless WHATSAPP_SKIP_SIGNATURE_VALIDATION=true is set. Always configure the App Secret for production. Find it at App Settings > Basic in your Meta App Dashboard.
Troubleshooting
Cause: Webhook not configured or not subscribed to the messages field.
Fix: In your Meta App Dashboard, go to WhatsApp > Configuration and verify the callback URL matches your tunnel URL (e.g., https://your-tunnel.ngrok.io/whatsapp/webhook). Click "Manage" and confirm you're subscribed to the messages field.
Cause: Invalid App Secret or missing signature validation config.
Fix: Verify WHATSAPP_APP_SECRET matches the value under App Settings > Basic in your Meta App Dashboard. Use WHATSAPP_SKIP_SIGNATURE_VALIDATION=true only for isolated localhost payload tests that are not network-reachable.
Cause: Verify token mismatch or server not running.
Fix: Ensure WHATSAPP_VERIFY_TOKEN matches the value you entered in the Meta webhook configuration. Your server must be running when you click "Verify and save".
Cause: Expired or insufficient access token.
Fix: Temporary tokens expire after ~24 hours. For production, create a permanent System User token via Meta Business Manager. Ensure the token has whatsapp_business_messaging permission.
Cause: Missing environment variables or agent errors.
Fix: Check application logs. Common causes: WHATSAPP_ACCESS_TOKEN not set, expired token, or an error in your agent's tools or model configuration.
Cause: macOS system Python missing root certificates.
Fix: Set the SSL_CERT_FILE environment variable:
export SSL_CERT_FILE=$(python3 -c "import certifi; print(certifi.where())")Developer Resources
Interface Reference
All parameters, endpoints, and session handling details.
Setup Guide
Create a Meta App, configure webhooks, and deploy step by step.
WhatsAppTools Reference
Toolkit parameters and methods for interactive messages, media, and locations.
Usage Examples
Agents, teams, media, image generation, reasoning, and more.