Skip to content
Start free

Wabery CLI

The Wabery CLI manages channels, registration intents, contacts, imports, broadcasts, messages, flows, hosted functions, automations, and webhooks from your terminal. It also ships the Wabery MCP server, so you can hand Wabery to an AI coding agent (Claude, Codex, OpenCode) and build by describing the feature.

Install it globally, then sign in:

Terminal window
npm i -g @wabery/cli
wabery login

wabery login authorizes the CLI in your browser (--no-open prints a URL instead), asks you to choose a project, creates a key scoped to that project, then shows the key once. Paste it back into the waiting terminal; the CLI stores it locally so future CLI and MCP commands are authenticated. Run wabery doctor to confirm. To override, set WABERY_API_KEY (and optionally WABERY_BASE_URL, which defaults to the API base https://api.wabery.com/v1 — not the dashboard origin).

Prefer not to install globally? Run any wabery command on demand through your package runner — handy for CI or a one-off wabery login:

Terminal window
npx wabery login
npx wabery doctor

After login, wabery init creates starter files for your app without overwriting existing files:

Terminal window
wabery init --name "My project"

It writes .env.wabery, wabery.config.json, and a wabery-webhook-handler.ts stub. Load .env.wabery in your app, edit the webhook URL in wabery.config.json, then apply it with wabery config apply wabery.config.json. Config apply can create or update a hosted-agent Draft only. It cannot edit Live or publish the agent to customers.

Terminal window
# verify your API key and base URL; also flags channels that drop inbound
# (the `channels` check lists any `routing_warnings`)
wabery doctor
# confirm the project this key is scoped to
wabery projects list
# for a new sandbox project: text the join code to the sandbox,
# send one normal WhatsApp message, then inspect the conversation
wabery projects get "project_id" # read join_code + enrollment_channel.phone_number
wabery channels list
wabery conversations list
wabery conversations messages "conversation_id"
# reply inside the open messaging window
wabery messages send --channel-id "channel_id" \
--conversation-id "conversation_id" \
--text "Thanks for your message"
# send richer WhatsApp payloads
wabery messages send --channel-id "channel_id" \
--conversation-id "conversation_id" \
--latitude 37.7749 --longitude -122.4194 --location-name "Office"
wabery messages send --channel-id "channel_id" \
--conversation-id "conversation_id" \
--sticker-link "https://example.com/sticker.webp"
# create a starter config, then validate it
wabery config init wabery.config.json --project-id "project_id"
wabery config validate wabery.config.json
# enroll a WhatsApp contact for testing (optionally with a language)
wabery contacts enroll --project-id "project_id" --phone "+14155550123" \
--preferred-language "es"
# update a contact's language or metadata later (metadata is shallow-merged)
wabery contacts update "contact_id" --preferred-language "id" \
--metadata '{"linked": true}'
# import a consented CSV audience, then poll the returned import id
wabery contacts import contacts.csv --phone-column phone \
--name-column name --language-column language --tags-column tags \
--consent '{"categories":["MARKETING"],"source":"signup_form","method":"web_form"}'
wabery contacts import-status "import_id"
# mint a single-use WhatsApp registration link
wabery registration-intents create --customer-reference "user_123" \
--metadata '{"plan": "starter"}'
wabery registration-intents get "intent_id"
# send a flow; use --config-key to pick the variant for the contact's language
wabery flows send "flow_id" --channel-id "channel_id" --contact-id "contact_id"
wabery flows send --config-key "lead_intake" --locale "es" \
--channel-id "channel_id" --contact-id "contact_id"
# check a queued message or wait for a template approval in scripts
wabery messages get "message_id"
wabery templates wait "template_id" --wait=86400
# inspect channels; `routing_warning` flags a channel that silently drops inbound
wabery channels list
wabery channels get "channel_id"
# forward a dedicated number's inbound to your webhook (overrides project routing)
wabery channels update "channel_id" --routing-mode EXTERNAL
# or set the project-wide default that new channels inherit
wabery projects update "project_id" --routing-mode EXTERNAL
# deploy hosted logic for tools or message-triggered workflows
wabery functions create --slug quote-customer \
--name "Quote customer" --trigger-type ANY_MESSAGE
wabery functions deploy "function_id" --source ./quote-customer.ts
wabery functions update "function_id" --active true
wabery functions test "function_id" --text "I need a quote"
wabery functions update "function_id" \
--expose-as-mcp-tool true \
--input-schema '{"type":"object"}'
wabery functions invoke "function_id" --arguments '{"orderId":"ord_123"}'
wabery functions logs "function_id"

A broadcast sends an approved WhatsApp template to a frozen, consent-eligible audience. It requires a dedicated Meta Cloud API channel; the shared sandbox cannot send broadcasts. Prepare and inspect the audience before the irreversible send step.

First create broadcast.json:

{
"name": "August launch",
"channelId": "channel_...",
"category": "MARKETING",
"templateName": "august_launch",
"fallbackLanguage": "en_US",
"variants": [
{
"templateId": "template_...",
"language": "en_US",
"parameterMappings": [
{
"component": "body",
"index": 0,
"parameterType": "text",
"source": "name",
"fallback": "there"
}
]
}
]
}

Then create, prepare, and review the draft:

Terminal window
wabery broadcasts create --body @broadcast.json
# audience.json must contain exactly one of contactIds, importId, or filter
wabery broadcasts prepare "broadcast_id" \
--body '{"filter":{"tags":["launch-list"],"languages":["en","es"]}}'
# preparation is asynchronous: poll until status is ready
wabery broadcasts get "broadcast_id"
# inspect the frozen snapshot, including skipped or failed recipients
wabery broadcasts recipients "broadcast_id"
wabery broadcasts recipients "broadcast_id" --limit 100
wabery broadcasts recipients "broadcast_id" --status skipped

Only after the returned counts match the intended audience, send now or schedule an absolute instant:

Terminal window
wabery broadcasts send "broadcast_id" \
--idempotency-key "august-launch-send"
wabery broadcasts schedule "broadcast_id" \
--scheduled-at "2026-08-01T10:00:00Z" \
--timezone "Europe/Amsterdam" \
--idempotency-key "august-launch-schedule"

Monitor, reuse, or stop a campaign:

Terminal window
wabery broadcasts list --status sending
wabery broadcasts get "broadcast_id"
wabery broadcasts duplicate "broadcast_id"
wabery broadcasts cancel "broadcast_id"

The CLI generates an idempotency key for mutating broadcast commands when one is not supplied. Pass your own stable key in scripts. Duplicating copies the template configuration into a new draft, not the previous audience snapshot. Canceling stops recipients that have not been dispatched; messages already accepted by Meta cannot be recalled. See WhatsApp broadcasts for consent, frequency, status, and webhook behavior.

The CLI also prints config for the local Wabery MCP server, so Claude Code, Codex, and OpenCode can call Wabery directly:

Terminal window
wabery mcp-config claude --raw
wabery mcp-config codex --raw
wabery mcp-config opencode --raw

Prefer no install? Wabery also has a hosted MCP server with OAuth sign-in. See the WhatsApp MCP Server docs for the hosted setup, the full list of exposed tools, and a worked example prompt.