Twilio WhatsApp Channel

Twilio WhatsApp channel plugin for OpenClaw

Audits

Pass

Install

openclaw plugins install clawhub:@srinathh/openclaw-channel-twilio-whatsapp

OpenClaw Twilio WhatsApp Channel

A channel plugin for OpenClaw that connects your AI agent to WhatsApp via the Twilio Business API.

npm version License: Apache-2.0

Why Twilio over Baileys?

OpenClaw ships with a built-in WhatsApp channel based on Baileys, which reverse-engineers the WhatsApp Web protocol. Baileys is convenient — no business verification, no monthly fees — but the trade-offs are real:

ConcernBaileysTwilio (this plugin)
Protocol stabilityBreaks when WhatsApp changes their internal protocolOfficial, versioned API
Account safetyRisk of bans for "automated" behaviorCompliant Business API
Delivery receiptsBest-effortFirst-class status callbacks
Group messagingYesNo (1:1 DMs only)
CostFreePer-message fees
SetupQR-code pairingSender registration + webhook

Pick this plugin when you need stability and compliance — for personal automations or when you need group chat, the bundled Baileys channel is simpler.

Features

  • Inbound webhooks via OpenClaw's gateway (no separate HTTP server)
  • Twilio signature validation on every inbound request
  • Allowlist enforcement so only approved phone numbers can talk to your agent
  • Inbound media download with redirect-following Basic Auth
  • Outbound media staging — local files are served back to Twilio via UUID-randomized URLs
  • Message chunking at Twilio's 1600-char limit
  • WhatsApp formatting hints injected into the agent prompt (*bold*, _italic_, etc.)
  • Fire-and-forget webhook responses — TwiML returned immediately, processing happens async (avoids Twilio's 15s timeout)

Installation

This plugin is loaded by OpenClaw at runtime via npm. Add it to your OpenClawInstance CRD:

apiVersion: openclaw.rocks/v1alpha1
kind: OpenClawInstance
metadata:
  name: openclaw
spec:
  plugins:
    - "@srinathh/openclaw-channel-twilio-whatsapp@latest"

Or in a non-Kubernetes deployment, install it into your OpenClaw runtime's node_modules:

npm install @srinathh/openclaw-channel-twilio-whatsapp@latest

Then add it to the plugins.load.paths array in openclaw.json (see Configuration).

Configuration

openclaw.json

{
  "channels": {
    "twilio-whatsapp": {
      "enabled": true,
      "dmPolicy": "allowlist",
      "allowFrom": ["+14155551234", "+14155555678"],
      "fromNumber": "+14155550000",
      "webhookUrl": "https://your-public-host.example.com"
    }
  },
  "plugins": {
    "enabled": true,
    "allow": ["@srinathh/openclaw-channel-twilio-whatsapp"],
    "load": {
      "paths": ["~/.openclaw/node_modules/@srinathh/openclaw-channel-twilio-whatsapp"]
    },
    "entries": {
      "@srinathh/openclaw-channel-twilio-whatsapp": { "enabled": true }
    }
  }
}
FieldRequiredDescription
enabledyesActivate the channel
dmPolicyyes"allowlist" (only allowFrom numbers) or "open" (anyone)
allowFromwhen allowlistPhone numbers in E.164 format (e.g. +14155551234)
fromNumberyesYour Twilio WhatsApp sender in E.164
webhookUrlyesPublic base URL where Twilio can reach OpenClaw — used both for signature validation and media serving

All phone numbers use E.164 format without the whatsapp: prefix — the plugin prepends it internally when calling Twilio.

Environment variables (secrets)

VariableRequiredDescription
TWILIO_ACCOUNT_SIDyesYour Twilio account SID (starts with AC)
TWILIO_AUTH_TOKENyesYour Twilio auth token

Twilio setup

1. Get a WhatsApp sender

For development, use the Twilio Sandbox for WhatsApp. For production, register a WhatsApp sender.

2. Configure the inbound webhook

In the Twilio Console for your WhatsApp sender, set:

  • When a message comes in: https://<your-host>/webhook/twilio-whatsapp
  • Method: HTTP POST

The path is fixed by this plugin. The host must match webhookUrl in your OpenClaw config exactly — Twilio's signature validation requires the URL to match.

3. Verify the health endpoint

curl https://<your-host>/webhook/twilio-whatsapp/health
# {"status":"ok","channel":"twilio-whatsapp"}

4. Send a test message

WhatsApp the number you registered in fromNumber from a phone in your allowFrom list. The agent should reply.

Architecture

┌─────────────────┐      POST /webhook/twilio-whatsapp
│  Twilio API     │ ──────────────────────────────────► ┌──────────────────┐
│                 │ ◄────────── 200 TwiML <Response/> ── │ OpenClaw gateway │
└─────────────────┘                                      │  (this plugin)   │
        ▲                                                └────────┬─────────┘
        │  client.messages.create({...})                          │ dispatchInboundDirectDmWithRuntime
        │                                                         ▼
┌───────┴─────────┐                                       ┌──────────────┐
│ Outbound:       │ ◄──────────── deliver(payload) ────── │ Agent runtime│
│ sendText /      │                                       └──────────────┘
│ sendMedia       │
└─────────────────┘

HTTP routes registered

PathAuthPurpose
POST /webhook/twilio-whatsappplugin (signature-validated)Inbound from Twilio
GET /webhook/twilio-whatsapp/media/*pluginServes outbound media for Twilio to fetch
GET /webhook/twilio-whatsapp/healthpluginLiveness check

Media handling

Inbound media (Twilio → agent):

  • Downloaded with redirect-following Basic Auth
  • Saved to ~/.openclaw/media/twilio-whatsapp/inbound/<MessageSid>-<i><ext>
  • Path included in the MediaPath / MediaPaths envelope fields

Outbound media (agent → Twilio):

  • Local files are copied to ~/.openclaw/media/twilio-whatsapp/outbound/<uuid><ext>
  • Served via the media endpoint with parent-directory check (no traversal)
  • Twilio fetches the URL and forwards to WhatsApp

Inbound flow

  1. Twilio POSTs application/x-www-form-urlencoded body with Body, From, MessageSid, NumMedia, etc.
  2. Plugin validates X-Twilio-Signature against webhookUrl + path — rejects with 403 on mismatch
  3. Plugin checks From against allowFrom (with whatsapp: prefix stripped) — rejects with 403 if not allowed
  4. Plugin immediately responds with empty TwiML (<Response/>) so Twilio doesn't time out
  5. Plugin downloads any inbound media (async, after responding)
  6. Plugin calls dispatchInboundDirectDmWithRuntime with the message envelope
  7. Agent processes the message and sends a reply via sendText / sendMedia

Development

git clone https://github.com/srinathh/openclaw-channel-twilio-whatsapp.git
cd openclaw-channel-twilio-whatsapp
npm install
npm run build

Project layout

src/
├── index.ts          # defineChannelPluginEntry — plugin entry point
├── channel.ts        # createChatChannelPlugin — main plugin definition
├── webhook.ts        # Twilio webhook handler (signature validation + dispatch)
├── media.ts          # download / stage / serve media
├── runtime.ts        # createPluginRuntimeStore — runtime accessor for dispatch
├── util.ts           # phone formatting + form body parsing
└── openclaw-sdk.d.ts # ambient type declarations for openclaw/plugin-sdk/*

Testing locally

You'll need an OpenClaw instance running with this plugin installed. The simplest setup:

# 1. In one terminal: build and link
npm run build
npm link

# 2. In your OpenClaw instance directory
npm link @srinathh/openclaw-channel-twilio-whatsapp

# 3. Add to your openclaw.json plugins config (see Configuration)
# 4. Use a tunnel (cloudflared, ngrok) to expose the gateway
# 5. Point Twilio's webhook at the tunnel URL

Compatibility

  • OpenClaw: requires openclaw >= 2026.3.28 (uses the plugin-sdk/* subpath imports)
  • OpenClaw operator (k8s): requires v0.30.0+ for the plugin peerDependency symlink
  • Node.js: 20+

Known limitations

  • DMs only — no group chat (Twilio's WhatsApp Business API doesn't support groups)
  • No reactions / typing indicators — Twilio doesn't expose these
  • No threaded replies — WhatsApp threading not exposed by Twilio
  • Single account — multiple Twilio accounts aren't supported in this version

License

Apache-2.0 — see LICENSE.

Contributing

Issues and PRs welcome at github.com/srinathh/openclaw-channel-twilio-whatsapp.