12 KiB
@elizaos/plugin-whatsapp
WhatsApp connector for elizaOS agents — supports WhatsApp Cloud API (Meta Business) and Baileys (QR-code personal account auth).
Purpose / Role
Adds WhatsApp messaging to any Eliza agent. The plugin registers WhatsAppConnectorService (the main send/receive engine) and WhatsAppWorkflowCredentialProvider (supplies credentials to the workflow plugin). It is opt-in: the plugin auto-enables when a connectors.whatsapp block is present in agent config and not explicitly disabled, or it can be loaded manually in a character file.
Plugin Surface
Services
| Name | Class | Description |
|---|---|---|
whatsapp |
WhatsAppConnectorService |
Manages Cloud API and Baileys clients, routes inbound messages through runtime.messageService, exposes sendMessage, webhook verification, and the full MessageConnector protocol |
workflow_credential_provider |
WhatsAppWorkflowCredentialProvider |
Resolves whatsAppApi credentials (access token + phone number ID) for the workflow plugin |
Routes (registered with rawPath: true)
| Method | Path | Description |
|---|---|---|
| GET | /api/whatsapp/webhook |
Meta webhook subscribe verification (public, no auth) |
| POST | /api/whatsapp/webhook |
Incoming Meta webhook events; validates X-Hub-Signature-256 before dispatch |
| POST | /api/whatsapp/pair |
Start a Baileys QR-pairing session (writes auth state, updates connector config on connect) |
| GET | /api/whatsapp/status |
Pairing session + service connection status |
| POST | /api/whatsapp/pair/stop |
Cancel an active pairing session |
| POST | /api/whatsapp/disconnect |
Logout and remove Baileys auth state |
Connector Capabilities
WhatsAppConnectorService registers with runtime.registerMessageConnector with capabilities: send_message, read_messages, search_messages, send_reaction, contact_resolution, chat_context, get_user. Supported target kinds: phone, contact, user, group, room.
No actions or evaluators
actions: [] — messaging is surfaced through the connector protocol, not standalone plugin actions.
Layout
plugins/plugin-whatsapp/
src/
index.ts Plugin entry: registers plugin object, re-exports public API
runtime-service.ts WhatsAppConnectorService — core send/receive engine, multi-account support
setup-routes.ts HTTP routes for webhook + QR pairing
connector-account-provider.ts ConnectorAccountManager adapter (list/create/patch/delete accounts)
workflow-credential-provider.ts Supplies whatsAppApi credentials to workflow plugin
config.ts TypeScript config types (WhatsAppChannelConfig, WhatsAppAccountConfig, etc.)
accounts.ts Multi-account resolution: resolveWhatsAppAccount, listEnabledWhatsAppAccounts
pairing-service.ts WhatsAppPairingSession — Baileys QR pairing state machine
normalize.ts Phone/JID normalization utilities (normalizeE164, chunkWhatsAppText, etc.)
media.ts Media URL validation helpers (assertValidWhatsAppMediaLink)
types.ts Raw transport types (NormalizedMessage, WhatsAppWebhookEvent, etc.)
webhook-auth.ts X-Hub-Signature-256 verification helper
client.ts WhatsAppClient — Cloud API HTTP client
clients/
factory.ts ClientFactory.create() — selects BaileysClient or WhatsAppClient
baileys-client.ts Baileys (personal WA) WebSocket client
interface.ts IWhatsAppClient interface
api/
whatsapp-routes.ts QR-flow route helpers (applyWhatsAppQrOverride, handleWhatsAppRoute)
services/ Additional service helpers
baileys/ Baileys-specific auth/store adapters
utils/ config-detector, misc helpers
auto-enable.ts Auto-enable check (shouldEnable); env-read only, no service init
package.json
build.ts
Commands
bun run --cwd plugins/plugin-whatsapp build # compile dist/
bun run --cwd plugins/plugin-whatsapp dev # hot-reload build (bun --hot)
bun run --cwd plugins/plugin-whatsapp test # vitest run
bun run --cwd plugins/plugin-whatsapp typecheck # tsgo --noEmit
bun run --cwd plugins/plugin-whatsapp lint # biome check --write
bun run --cwd plugins/plugin-whatsapp format # biome format --write
bun run --cwd plugins/plugin-whatsapp clean # rm -rf dist .turbo
Config / Env Vars
Config is read from runtime.getSetting(key) first, then process.env[key]. All keys are listed in agentConfig.pluginParameters in package.json.
Cloud API (Meta Business) transport
| Env var | Required | Description |
|---|---|---|
WHATSAPP_ACCESS_TOKEN |
Yes | Long-lived Cloud API access token from Meta Business Manager |
WHATSAPP_PHONE_NUMBER_ID |
Yes | Phone number ID registered in Meta Business |
WHATSAPP_APP_SECRET |
Yes (webhooks) | App Secret for X-Hub-Signature-256 verification on webhook POSTs |
WHATSAPP_WEBHOOK_VERIFY_TOKEN |
No | Token for Meta's one-time GET webhook subscribe handshake |
WHATSAPP_BUSINESS_ACCOUNT_ID |
No | WABA ID (informational) |
WHATSAPP_API_VERSION |
No | Graph API version string (default: v24.0) |
Baileys (personal account / QR) transport
| Env var | Required | Description |
|---|---|---|
WHATSAPP_AUTH_DIR |
Yes (Baileys) | Directory for multi-file Baileys auth state |
WHATSAPP_SESSION_PATH |
No | Alternative name for WHATSAPP_AUTH_DIR |
WHATSAPP_AUTH_METHOD |
No | Force transport (cloudapi / baileys); overrides auto-detection |
Access control (both transports)
| Env var | Default | Description |
|---|---|---|
WHATSAPP_DM_POLICY |
pairing |
open / allowlist / pairing / disabled |
WHATSAPP_GROUP_POLICY |
allowlist |
open / allowlist / disabled |
WHATSAPP_ALLOW_FROM |
— | Comma-separated E.164 numbers for DM allowlist |
WHATSAPP_GROUP_ALLOW_FROM |
— | Comma-separated E.164 numbers for group sender allowlist |
Agent behavior
| Env var | Default | Description |
|---|---|---|
WHATSAPP_AUTO_REPLY |
false |
When true, inbound messages trigger agent reply. Off by default — messages are stored in memory only unless auto-reply is explicitly enabled or the connector is invoked via the message connector protocol |
Multi-account (character settings only)
Configure multiple accounts under character.settings.whatsapp.accounts.<id> using the fields from WhatsAppAccountConfig (src/config.ts). Each account entry mirrors the env-var fields above plus display name, per-group config, and chunking options.
How to Extend
Add a new route
- Write a handler
async function handleX(req, res, runtime)insrc/setup-routes.tsor a new file. - Add a
Routeentry towhatsappSetupRouteswithrawPath: trueif the path must not be prefixed. - The routes array is imported in
src/index.tsand registered by the runtime.
Add a new capability to the connector
- Open
src/runtime-service.ts→WhatsAppConnectorService.registerSendHandlers. - Add the capability string to the
capabilitiesarray in theregisterMessageConnectorIfAvailablecall. - Implement the handler method on
WhatsAppConnectorServiceand wire it into the registration object.
Add a new service
- Extend
Servicefrom@elizaos/corein a newsrc/file. - Import the class in
src/index.tsand add it towhatsappPlugin.services.
Conventions / Gotchas
- Transport detection:
WHATSAPP_AUTH_METHOD(cloudapi/baileys) wins when set. OtherwiseWHATSAPP_AUTH_DIRpresent → Baileys;WHATSAPP_ACCESS_TOKEN+WHATSAPP_PHONE_NUMBER_IDpresent → Cloud API. Baileys takes precedence when both are set (seeresolveRuntimeConfiginruntime-service.ts, transport resolution inaccounts.ts). - Auto-reply is off by default. Inbound messages are stored in memory. The agent only replies when
WHATSAPP_AUTO_REPLY=trueor when the connector is triggered through the message connector protocol (e.g., a workflow or orchestrator sends onsource: "whatsapp"). - Webhook security: Cloud API webhook POSTs are rejected without a valid
X-Hub-Signature-256(usesWHATSAPP_APP_SECRET). The GET verification route is public by design (Meta requires it). - Bundle safety:
src/index.tscontains a large__bundle_safety_*array that force-binds re-exported names into the module init. Do not remove it — Bun's tree-shaker collapses re-exports into empty inits on mobile without it. - External deps:
@whiskeysockets/baileys(Baileys WS),qrcode/qrcode-terminal(QR display),pino(Baileys logger). All are runtime deps. No native binaries. - Text chunking: Outbound text is split into chunks of ≤4096 chars by default (
WHATSAPP_TEXT_CHUNK_LIMITconstant insrc/normalize.ts). Groups can overridechunkModeto"newline". - Pairing session limit: Maximum 10 concurrent Baileys QR pairing sessions (
MAX_PAIRING_SESSIONSinsetup-routes.ts). - For repo-wide architecture rules, logger conventions, and ESM requirements see the root
AGENTS.md.
⛔ NON-NEGOTIABLE — evidence, trajectories & real end-to-end tests
The binding, repo-wide standard is AGENTS.md. Read it. Nothing in this package is done until it is proven done — a reviewer must confirm it works without reading the code, from the artifacts you attach. This applies to every feature, fix, refactor, and chore here. "Tests pass" is not proof; "CI is green" is not proof.
- Record AND read model trajectories. Capture the actual inputs and outputs of the model
from a live LLM — not the deterministic proxy, not a mock: the prompt, the
providers/context, the raw model output, every tool/action call, and the result. Then open
the trajectory and review it by hand. A captured-but-unread trajectory is not evidence
(
packages/scenario-runner/bin/eliza-scenarios run <scenario> --report <out>). - Real, full-featured E2E — no larp. Every feature ships detailed end-to-end tests that drive the real path end to end. Not the happy "front door" only: cover error paths, edge/empty/invalid input, concurrency, roles/permissions, and adversarial input. A test that asserts against a mock/stub/fixture standing in for the thing under test does not count. If the real model/device/chain/connector/account is hard to reach, make it reachable — that is the work, not an excuse to mock. If the existing tests here are shallow or mocked, fixing them is part of your change.
- Screenshots + logs at every phase, plus a complete walkthrough video/run-through of
the entire feature or view, start to finish (
bun run test:e2e:record). - Manually review every artifact the change touches — never just the green check: client
logs (console + network), server logs (
[ClassName] …), the model trajectories in and out, before/after full-page screenshots, and the domain artifacts listed below for this package. - No residuals. No shortcuts. The goal is not "done" — it is everything done. Clear every blocker by the hard path: build the real architecture, stand up the real model/device/service, actually test it. Never leave a TODO, a stub, a stepping-stone, or a "follow-up." When unsure, research thoroughly, weigh the options, and ship the best, highest-effort, production-ready version. Keep going until every possibility is exhausted.
Artifacts → attached inline in the PR (MP4 video, JPG screenshots, logs in <details>); attach each evidence type or
explicitly mark it N/A with a reason — never leave it blank. If develop moved and changed
behavior, re-capture evidence; stale proof is worse than none.
Capture & manually review for this package — platform connector:
- A real (or sandbox-account) round-trip on the platform: inbound message → agent → outbound reply, captured as logs and a screenshot/recording of the actual conversation.
- The raw inbound event/webhook payload and the outbound API request/response, with IDs mapped correctly (
stringToUuid/createUniqueUuid). - Attachments, threads/replies, edits, multi-account, and rate-limit/error paths — not just a single text ping.
- The agent trajectory for the turn the connector drove.