Skip to main content
The WhatsApp interface connects agents to WhatsApp with two transport providers:
  • Twilio — Managed webhook + REST API. Paid. Production-ready. No QR login.
  • Baileys — Self-hosted via Node.js sidecar. Free. Full protocol access (polls, reactions, groups, QR login).

Quick Start (Baileys)

The fastest way to get a WhatsApp agent running — no API keys, no webhooks, just scan a QR code.
import asyncio
from definable.agent import Agent
from definable.agent.interface.whatsapp import WhatsAppInterface

agent = Agent(
    model="openai/gpt-4o-mini",
    instructions="You are a helpful assistant.",
)

whatsapp = WhatsAppInterface(
    provider="baileys",
    auth_dir="./whatsapp-auth",
)

whatsapp.bind(agent)

async def main():
    await whatsapp.start()
    print("Scan the QR code above with WhatsApp on your phone.")
    try:
        await asyncio.Event().wait()
    except KeyboardInterrupt:
        await whatsapp.stop()

asyncio.run(main())
On first run, a QR code appears in your terminal. Scan it with WhatsApp on your phone. After linking, credentials are saved to auth_dir and subsequent runs connect automatically.
Baileys requires Node.js >= 18 on the host. The bridge dependencies (@whiskeysockets/baileys, ws) are auto-installed on first run via npm install.

Quick Start (Twilio)

For production deployments using the Twilio WhatsApp Business API.
from definable.agent import Agent
from definable.agent.interface.whatsapp import WhatsAppInterface

agent = Agent(model="gpt-4o", instructions="You are a helpful assistant.")

whatsapp = WhatsAppInterface(
    provider="twilio",
    account_sid="AC...",
    auth_token="...",
    from_number="whatsapp:+14155238886",
)

agent.serve(whatsapp, port=8000)
Configure your Twilio WhatsApp webhook to point to http://your-server:8000/whatsapp/webhook.

Provider Comparison

FeatureTwilioBaileys
SetupAPI keys + webhookQR code scan
CostPaid (per message)Free
MediaURL-based onlyFull (bytes + URL)
PollsNoYes
ReactionsNoYes
GroupsNoYes
QR LoginNoYes
Typing indicatorNoYes
HostingManaged (webhook)Self-hosted (sidecar)

WhatsAppInterface Parameters

Provider Selection

provider
str
default:"twilio"
Transport provider: "twilio" or "baileys".

Twilio Parameters

account_sid
str
Twilio account SID. Required when provider="twilio".
auth_token
str
Twilio auth token. Used for REST API calls and webhook signature validation.
from_number
str
WhatsApp sender number (format: whatsapp:+14155238886). Required for Twilio.
validate_signatures
bool
default:"true"
Validate X-Twilio-Signature on incoming webhooks. Disable only for local testing.

Baileys Parameters

auth_dir
str
default:"./whatsapp-auth"
Directory for WhatsApp credential storage. Created automatically.
node_path
str
default:"node"
Path to the Node.js binary. Override if node is not on your PATH.
bridge_port
int
default:"0"
WebSocket port for the sidecar. 0 = auto-assign (recommended).
reconnect_max_attempts
int
default:"12"
Maximum reconnect attempts before giving up after a disconnect.
heartbeat_seconds
int
default:"60"
Heartbeat interval for the sidecar connection.

Shared Parameters

policy
WhatsAppPolicy
Sender access control policy. See Access Control below.
markdown_conversion
bool
default:"true"
Convert Markdown formatting in agent responses to WhatsApp-compatible formatting (**bold** to *bold*, etc.).
text_chunk_limit
int
default:"4000"
Maximum characters per message. Long responses are split at word boundaries.
webhook_path
str
default:"/whatsapp/webhook"
URL path for the Twilio webhook endpoint.
verbose
bool
default:"false"
Enable verbose logging in both Python and the Node.js sidecar.

Access Control

Use WhatsAppPolicy to control which messages reach the agent.
from definable.agent.interface.whatsapp import WhatsAppInterface, WhatsAppPolicy

whatsapp = WhatsAppInterface(
    provider="baileys",
    auth_dir="./whatsapp-auth",
    policy=WhatsAppPolicy(
        dm_policy="allowlist",
        allow_from=["+15551234567", "+15559876543"],
        group_policy="disabled",
    ),
)

Policy Options

dm_policy
str
default:"allowlist"
Direct message policy: "allowlist" (only allow_from), "open" (all DMs), or "disabled" (block all DMs).
allow_from
List[str]
Allowed sender phone numbers in E.164 format. Use "*" for wildcard.
group_policy
str
default:"open"
Group message policy: "open", "allowlist", or "disabled".
group_allow_from
List[str]
Separate allowlist for group senders. Falls back to allow_from if not set.
self_phone
str
Your own phone number (E.164). Used for self-chat detection.
The default dm_policy is "allowlist" with an empty allow_from list. This means no one can message your agent unless you explicitly add numbers. Set dm_policy="open" to allow all senders, or populate allow_from.

Media Support

Both providers handle media, but with different capabilities:
Media TypeTwilioBaileys
ImagesURL onlyURL + bytes
AudioURL onlyURL + bytes
VideoURL onlyURL + bytes
FilesURL onlyURL + bytes
Incoming media is converted to Definable’s Image, Audio, Video, and File types and passed to the agent.

Voice Notes

WhatsApp voice notes work with audio_transcriber=True:
agent = Agent(
    model="openai/gpt-4o-mini",
    instructions="You are a helpful assistant.",
    audio_transcriber=True,
)

whatsapp = WhatsAppInterface(provider="baileys", auth_dir="./whatsapp-auth")
whatsapp.bind(agent)

Sending Media

Agents can send media back via tool responses:
from definable.tool.decorator import tool
from definable.media import Image

@tool
def generate_chart(data: str) -> Image:
    """Generate a chart from data."""
    return Image(filepath="chart.png")

Markdown Conversion

When markdown_conversion=True (default), agent responses are automatically converted to WhatsApp-compatible formatting:
MarkdownWhatsApp
**bold***bold*
*italic*_italic_
~~strike~~~strike~
`code````code```
# Heading*Heading*
[text](url)text (url)
Fenced code blocks are preserved as-is (WhatsApp renders triple backticks natively).

Baileys Features

QR Login

The Baileys provider supports programmatic QR login:
# Start QR login
qr = await whatsapp.login_qr_start()
print(f"QR data: {qr.qr_data}")

# Wait for scan
result = await whatsapp.login_qr_wait(timeout_ms=60_000)
if result.connected:
    print("Connected!")

Polls

from definable.agent.interface.whatsapp.provider import PollMessage

await whatsapp.provider.send_poll(PollMessage(
    to="[email protected]",
    question="What's for lunch?",
    options=["Pizza", "Sushi", "Tacos"],
    allows_multiple=True,
))

Reactions

from definable.agent.interface.whatsapp.provider import ReactionMessage

await whatsapp.provider.send_reaction(ReactionMessage(
    chat_jid="[email protected]",
    message_id="msg-id-here",
    emoji="👍",
))

Health Check

status = await whatsapp.health()
print(f"Connected: {status.connected}")
print(f"Phone: {status.self_phone}")

Phone Number Normalization

Phone numbers are automatically normalized to bare E.164 (digits only, no +):
from definable.agent.interface.whatsapp.normalize import normalize_e164

normalize_e164("+1-555-123-4567")  # → "15551234567"
normalize_e164("whatsapp:+1555")   # → None (too short)

Complete Production Example

import asyncio
import os
import signal
from definable.agent import Agent
from definable.agent.interface.whatsapp import WhatsAppInterface, WhatsAppPolicy

agent = Agent(
    model="openai/gpt-4o",
    instructions="You are a customer support agent for Acme Corp.",
    audio_transcriber=True,
)

whatsapp = WhatsAppInterface(
    provider="baileys",
    auth_dir="./whatsapp-auth",
    policy=WhatsAppPolicy(
        dm_policy="allowlist",
        allow_from=[os.environ.get("MY_PHONE", "+15551234567")],
        group_policy="disabled",
    ),
    markdown_conversion=True,
    verbose=False,
)

whatsapp.bind(agent)

async def main():
    await whatsapp.start()
    print("[ready] WhatsApp agent is running. Press Ctrl+C to stop.")

    stop = asyncio.Event()
    loop = asyncio.get_running_loop()
    for sig in (signal.SIGINT, signal.SIGTERM):
        loop.add_signal_handler(sig, stop.set)
    await stop.wait()

    await whatsapp.stop()

asyncio.run(main())

Imports

from definable.agent.interface.whatsapp import WhatsAppInterface, WhatsAppPolicy

# Provider types (for advanced usage)
from definable.agent.interface.whatsapp.provider import (
    WhatsAppProvider, InboundMessage, OutboundMessage,
    PollMessage, ReactionMessage, SendResult,
    ConnectionStatus, QRLoginResult,
)

# Utilities
from definable.agent.interface.whatsapp.normalize import normalize_e164, redact_phone
from definable.agent.interface.whatsapp.formatting import markdown_to_whatsapp