WhatsApp

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

basic.py
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)

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:

  1. The X-Hub-Signature-256 header is extracted
  2. A signature is computed: HMAC-SHA256(app_secret, request_body)
  3. 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

Developer Resources