WhatsApp Integration
Connect agents to WhatsApp via Twilio. Users can send direct messages to the agent from any WhatsApp account.
Concepts
whatsapp:+14155238886). Free to use; users must opt in with a keyword before messaging.public_chat_url to be configured in Settings and a Cloudflare Tunnel ingress rule./login to access agents with restricted access policies.Prerequisites
Before connecting a Twilio account, two platform-level prerequisites must be in place — plus one Twilio Console setting if you want inbound attachments to work.
1. Public URL
Go to Settings → Public Chat URL and enter your Trinity instance's public domain (e.g., https://your-domain.com). Trinity uses this to generate the webhook URL shown in the UI.
2. Cloudflare Tunnel Ingress Rule
Add the following path rule in your Cloudflare Tunnel dashboard so Twilio webhooks reach the backend:
| Route | Backend |
|---|---|
| /api/whatsapp/webhook/* | http://backend:8000 |
The webhook is a backend FastAPI route (Twilio-signature verified). Routing it to the frontend service silently drops inbound messages — point it at the backend.
Without this rule, Twilio webhooks return 404 at the Cloudflare edge and never reach Trinity. The UI shows a yellow notice as a reminder.
Verify the route is working:
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://your-domain.com/api/whatsapp/webhook/test
# Should return 200, not 4043. HTTP Basic Authentication for Media (required for inbound attachments)
Only needed if users will send your agent images, documents, or voice notes. Text-only agents can skip this.
Most accounts already have this and need no action. Twilio enabled HTTP Basic Authentication for media on all newly-created main accounts from 2023-07-31, without the option to disable it. Only accounts created before that date can still have it off — check the Twilio Console (Messaging → Settings → General) if inbound attachments fail.
Twilio never serves media from api.twilio.com directly — it redirects to a CDN, and which CDN depends on this setting:
| Media auth | Redirect target | Trinity |
|---|---|---|
| Enabled | mms.twiliocdn.com (short-lived signed URL) | ✅ Supported |
| Disabled | s3-external-1.amazonaws.com | ❌ Refused |
Trinity refuses the S3 target on purpose: that hostname serves everyS3 bucket in the world under one name, so allowing it would let any URL claiming that host pull arbitrary content through Trinity's media fetcher. There is no setting to override this — enable media auth in Twilio instead.
Getting Twilio Credentials
You need three values from Twilio: Account SID, Auth Token, and a WhatsApp sender number.
Sandbox (Development)
Go to Twilio Console → Messaging → Try WhatsApp
Copy your Account SID and Auth Token from the top of the console
The sandbox sender is whatsapp:+14155238886 (shared across all Twilio sandbox users)
Note the sandbox join keyword shown on the Try WhatsApp page — users must send join <keyword> to the sandbox number before they can message your agent
Production
Twilio Console → Messaging → Senders → WhatsApp Senders
Register a sender — this requires linking a Meta Business Manager account
Display-name approval takes 24–48 hours
Once approved, copy the dedicated sender number in E.164 format (e.g., +14155001234)
Connect WhatsApp to an Agent
Open the agent detail page
Select the Sharing tab and find the WhatsApp row under Channels
Click Configure — the WhatsApp (Twilio) configuration opens in a dialog
- • Twilio Account SID — starts with
AC, 34 characters long - • Auth Token — from the Twilio Console (stored encrypted)
- • WhatsApp From Number — in format
whatsapp:+<E.164>, e.g.whatsapp:+14155238886
Click Connect
Trinity validates the credentials against Twilio and generates a webhook URL. If the validation fails, the error from Twilio is shown inline.
Wire the Webhook in Twilio
After connecting, a webhook URL appears in the UI. Copy it and paste it into Twilio:
Sandbox:
- • Twilio Console → Messaging → Try WhatsApp → Sandbox settings
- • Set "When a message comes in" to HTTP POST + your webhook URL
Production:
- • Twilio Console → Messaging → Senders → your registered sender
- • Set the webhook URL on the sender configuration
Sandbox Opt-In
For the sandbox, users must opt in before they can chat. Share these instructions with testers:
Open WhatsApp on your phone
Send a message to +1 415 523 8886
Type: join <your-sandbox-keyword> (shown in Twilio Console → Try WhatsApp)
Once Twilio confirms opt-in, the user can message the agent normally
Verify Credentials
On the connected state, click Verify to confirm the stored credentials are still valid (makes a live call to Twilio without sending a message).
Disconnect
Click Disconnectto remove the binding. The Twilio sender is not affected — only the Trinity configuration is removed.
Outbound Media & Files
Agents can deliver files and images in their WhatsApp replies, not just text. Each file is hosted transiently by Trinity (~1 hour) and sent as a Twilio media attachment — one message per file, since WhatsApp allows a single media item per message. The text reply always goes out first, so it survives even if a media send fails.
| File type | Delivered as | Size cap |
|---|---|---|
| Images, audio, video | Native media | 5 MB |
| Text and structured documents (txt, JSON, XML, PDF, YAML, SQL, …) | Document | 16 MB |
| Unknown binary types | Not deliverable as media | — |
📎 name: url download link appended to the text reply — it never blocks the reply or other files.Voice Replies (Outbound)
The agent can speak its replies as WhatsApp voice notes (OGG, delivered via Twilio media). Enable the shared Voice replies toggle inside the WhatsApp dialog — see Voice Replies. Voice notes work even when the file-sharing toggle is off; they are gated only by their own setting.
User Commands
WhatsApp users can send these commands to the agent number:
| Command | Action |
|---|---|
| /login user@example.com | Sends a 6-digit verification code to the email address |
| /login 123456 | Submits the code and verifies the email |
| /logout | Unlinks the verified email from this WhatsApp number |
| /whoami | Shows the currently verified email address |
After successful /login, if the agent has a restricted access policy (require_email enabled), Trinity checks whether the verified email is on the shared-access list. If not, an access request is automatically created and the user is notified that approval is pending.
Access Control
WhatsApp respects the same cross-channel access policy as Telegram and Slack. Configure it in Agent Detail → Sharing → Channel Access Policy:
| Policy | Effect |
|---|---|
| Open access | Any WhatsApp user can chat with the agent without verification |
| Require email | Users must complete /login before chatting; unverified users are prompted |
| Require sharing | Users must be explicitly shared on the agent (owner/admin must approve) |
Approving an access request (from Sharing → Access Requests) automatically adds the email to the shared-access list.
API
| Endpoint | Method | Description |
|---|---|---|
| /api/agents/{name}/whatsapp | GET | Binding status and webhook URL |
| /api/agents/{name}/whatsapp | PUT | Configure Twilio credentials |
| /api/agents/{name}/whatsapp | DELETE | Remove binding |
| /api/agents/{name}/whatsapp/test | POST | Verify credentials or send a test message |
Configure programmatically:
# Connect a Twilio account to an agent
curl -X PUT http://localhost:8000/api/agents/my-agent/whatsapp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"auth_token": "your-auth-token",
"from_number": "whatsapp:+14155238886"
}'
# Check binding status
curl http://localhost:8000/api/agents/my-agent/whatsapp \
-H "Authorization: Bearer $TOKEN"
# Verify credentials (no test message sent)
curl -X POST http://localhost:8000/api/agents/my-agent/whatsapp/test \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
# Send a test message to a specific number
curl -X POST http://localhost:8000/api/agents/my-agent/whatsapp/test \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"to_number": "whatsapp:+15551234567", "message": "Test from Trinity"}'Troubleshooting
"public_chat_url is not set" warning
Go to Settings → Public Chat URLand enter your Trinity instance's public domain. The webhook URL generates automatically once saved.
Twilio webhook returns 404
The Cloudflare Tunnel ingress rule for /api/whatsapp/webhook/* is missing or misconfigured. Add it in the Cloudflare dashboard pointing to the backend service (http://backend:8000).
HMAC signature validation fails
This means the URL Twilio signed does not match the URL Trinity sees. Check that:
- • nginx has
X-Forwarded-Proto $schemeconfigured - • uvicorn runs with
--proxy-headers - • The
public_chat_urlin Settings exactly matches the public URL Twilio posts to
Sandbox messages not arriving
- Confirm the user sent the sandbox join keyword from their phone
- Verify the webhook URL in Twilio Console → Try WhatsApp → Sandbox settings is set to HTTP POST
- Check backend logs for signature or binding errors
Credentials rejected
Twilio returns 401 if the AccountSid/AuthToken combination is invalid. Re-copy the Auth Token from the Twilio Console — it resets when you click "regenerate" in Twilio.
Inbound images arrive as "— download failed"
The agent got your message, but not the file. Check the backend log for:
[WHATSAPP] Refusing off-domain media redirect to host=s3-external-1.amazonaws.com
That host means HTTP Basic Authentication for media is not enabledon your Twilio account — see Prerequisites → 3 above. Turn it on in the Twilio Console; no Trinity restart is needed, the next attachment will work.
Any other host in that warning means Twilio changed its media CDN — that needs a Trinity fix, not a config change (the allowlist is intentionally not operator-configurable, because that fetch carries your Twilio Auth Token). Please open an issue with the logged hostname.
If the log shows no Refusing… line at all, the failure is elsewhere:
| Log line | Meaning |
|---|---|
| No credentials to download media for agent=… | The binding lost its Auth Token — re-save it |
| Media download failed (status=…) | Twilio-side error (401 = bad Auth Token, 404 = media expired) |
| Media redirect budget exhausted | Twilio chained more than 3 redirects — please open an issue |
| Media exceeds transport cap | The file is larger than 16 MB |
| Error downloading media …: ConnectError (or another type name) | Network/TLS failure reaching Twilio. Only the exception type is logged, deliberately: the signed media URL is a live credential for ~4 hours and some exception messages embed it |
Attachment says "unsupported format"
This is different from "download failed" — the file wasfetched successfully, then rejected by Trinity's file-type policy. PDFs, archives, video, and audio (including WhatsApp voice notes) are not accepted into agent workspaces on any channel. Text, CSV, JSON, and images are.
Limitations
63016. Approved message templates (Phase 3, not yet available) will lift this restriction for outbound-first use cases.auto). Agents sending proactive messages must specify channel="whatsapp" explicitly and the recipient must have a verified email linked to a prior WhatsApp conversation.