Send WhatsApp messages and statuses, manage contacts, groups, webhooks and connected numbers, and send WABA templates through 2Chat.
2Chat ships in the w6w first-party pack. It declares 28 actions, 2 health checks, and the host runs its code in a sandbox that never sees the credential.
io.w6w.2chat2Chat is a WhatsApp messaging-automation platform with a REST API. It runs two kinds of WhatsApp channel side by side: WhatsApp Web numbers, which are ordinary phones paired by QR code (so a workflow can message any contact, join groups and post statuses), and WhatsApp Business API (WABA) numbers, which use Meta-approved templates and a 24-hour customer-service window.
This app covers the account-level surface a workflow needs: contacts (create, list, get, update, delete, search), webhook subscriptions, the connected-number list and its connection status and connect/disconnect commands, WhatsApp Web messaging (send text, media or a map pin to a person or a group; list messages and conversations; read a message’s delivery state; delete a message), number checking, statuses, groups and their participants, and WABA template messaging (send, list templates, check the conversation window).
Every path, verb and field was read on 2026-10-06 from 2Chat’s own developer reference (developers.2chat.co, indexed by llms.txt) and cross-read against the vendor’s agent skills; where the two disagree the README says which spelling the app sends. Every API call spends one credit on the account’s plan. See the README for what was left out (catalog, SMS, phone numbers and calls, Slack linking, WABA template creation) and the vendor quirks that shaped the design.
Three routes to the same 28 actions. The Workflow tab is generated from 2Chat's own manifest and carries its real ids, so it is copy-pasteable; the Code and CLI examples are the same call for any action on any app, so every app-specific value in them is a blank you fill in.
channel-command Run a command on a connected number: connect brings it online and starts a QR code; disconnect takes it offline without deleting it (POST /whatsapp/channel/{channel-uuid}/{command}). A connect needs a human to scan the QR with the phone.
channel-status-get Get a connected number's connection status and its last 10 events (GET /whatsapp/channel/{channel-uuid}/status). 2Chat refreshes this about every 3 seconds. connection_status: C connected, D disconnected, F failure.
contact-create Create a contact (POST /contacts). Optionally also create it on a connected WhatsApp number by passing its channel UUID. Not idempotent: a retry creates a second contact.
contact-delete Delete a contact from the 2Chat directory (DELETE /contacts/{contact-uuid}).
contact-update Edit a contact (PUT /contacts/{contact-uuid}). Only the fields you supply are sent. Setting a channel UUID syncs the contact to that WhatsApp number.
contacts-list List contacts, paginated (GET /contacts). Optionally restrict to the contacts of one connected number.
contacts-search Search contacts by phone number or name (GET /contacts/search), paginated.
conversations-list List a connected number's conversations, 10 per page (GET /whatsapp/conversations/{channel-uuid}). Poll this to pick up new conversations without a webhook.
group-create Create a WhatsApp group from a connected number with up to 10 participants (POST /whatsapp/group/create). WhatsApp soft-limits group creation (about 30 a day) and shadow-bans numbers that create many. Not idempotent. Check `unaccepted_numbers` in the result.
group-get Get a WhatsApp group's details and participant list (GET /whatsapp/group/{group-uuid}).
group-messages-list List the messages of a WhatsApp group, newest first, 50 per page (GET /whatsapp/groups/messages/{group-uuid}).
group-participants-update Add, remove, promote to admin or demote participants of a WhatsApp group, up to 10 per call (POST /whatsapp/group/{group-uuid}/{add|remove|promote|demote}-participant). Read the per-number result, including `unaccepted_numbers`.
groups-list List the WhatsApp groups a connected number is in (GET /whatsapp/groups/{phone-number}). A fresh connection can take 5 to 30 minutes to show its groups.
message-delete Delete a message from 2Chat and from WhatsApp (DELETE /whatsapp/message/{session-key}/{message-uuid}). WhatsApp only honours it for 60 hours after sending; in a group, only an admin can delete someone else's message and WhatsApp otherwise silently ignores it.
message-get Get one message and its delivery state (GET /whatsapp/message/{session-key}/{message-uuid}): sent, received, read, wa_msg_ack 0-3.
message-send Send a WhatsApp Web message from a connected number to a phone number, a username ID or a group (POST /whatsapp/send-message): text, a media file by public URL, or a map pin. Queued, not delivered, when it returns. Not idempotent: a retry sends a second message. Billed per call.
messages-list List the messages on a connected number, newest first, 100 per page (GET /whatsapp/messages/{your-number}[/{remote-number}]). Only messages after the number was connected to 2Chat exist.
number-check Check whether a phone number has a WhatsApp account (GET /whatsapp/check-number/{your-number}/{number-to-check}). Counts against a separate number-check quota. If `number_id` differs from the number you checked, send to `number_id`.
numbers-list List the WhatsApp Web numbers connected to 2Chat (GET /whatsapp/get-numbers). These are QR-paired phones, not WhatsApp Cloud API numbers.
status-post Post an ephemeral WhatsApp status (a story) from a connected number: text with optional colour and font, an image, or a video by public URL (POST /whatsapp/set-{text|image|video}-status/{number}). Queued, not published, when it returns.
users-list List the active users on the 2Chat account (GET /users). Pending, suspended and deleted users are filtered out by 2Chat.
waba-conversation-window-get Check whether the 24-hour customer-service window is open between a WABA number and a contact (GET /waba/conversation-window/{from}/{to}). Open: free-form text is allowed; closed: templates only.
waba-message-send Send a WhatsApp Business API (WABA) message: an approved template (starts a conversation or reopens one after 24 hours) or a free-form text inside the open 24-hour window (POST /waba/send-message). Answers 202 once queued. Not idempotent. Billed per call and per WABA credit.
waba-templates-list List the message templates of a WABA number, 10 to 200 per page (GET /waba/templates). Only APPROVED templates can be sent.
webhook-subscribe Subscribe a URL to a 2Chat event (POST /webhooks/subscribe/{event-name}) for a connected WhatsApp number — WhatsApp Web (`whatsapp.*`) or WABA (`whatsapp.waba.*`) events. Not idempotent: each call creates another subscription.
webhooks-list List the enabled webhook subscriptions on the account (GET /webhooks), or those of one channel (GET /webhooks/channel/{channel-uuid}).
A workflow step names the app and the action, and the editor fills in the
connection when you pick one. This is the Step shape from the
workflow spec, carrying 2Chat's real ids.
{
"manifestVersion": "2",
"name": "2chat-example",
"steps": [
{
"id": "channel-command",
"uses": {
"app": "io.w6w.2chat",
"action": "channel-command",
"connection": "conn_YOUR_CONNECTION_ID"
},
"with": {
"channelUuid": "<channelUuid>",
"command": "<command>"
}
}
]
}channel-command channel-status-get contact-create contact-get contact-update +23 more actions available
Every app-specific value here is a blank you have to fill in. An
app action is reached through the connection that authenticates it, so the
address is a connection id, not the app id — and connections belong to your account,
so a public page cannot know yours. Create one for 2Chat, then fill in
the three blanks: conn_YOUR_CONNECTION_ID, the action key, and the
parameters that action declares. The call itself is real — the shape is transcribed
from the studio's own snippet builder, which prints the same kind of blanks — but
nothing in it is specific to 2Chat. The Workflow tab is where this app's
real ids are.
npm install @w6w/sdkyarn add @w6w/sdkpnpm add @w6w/sdkdeno add npm:@w6w/sdkimport { W6wClient, isActionRun } from "@w6w/sdk";
// Reads W6W_BASE_URL and W6W_TOKEN from the environment when omitted.
const client = new W6wClient();
const envelope = await client.run({
urn: "conn_YOUR_CONNECTION_ID",
action: "channel-command",
payload: {
channelUuid: "<value>",
command: "<value>",
},
});
if (isActionRun(envelope)) console.log(envelope.value); npm install -g @w6w/cli w6w run conn_YOUR_CONNECTION_ID --action channel-command --payload '{"channelUuid":"<value>","command":"<value>"}' Give an AI agent 2Chat — without giving it 2Chat's credentials. One MCP endpoint exposes every app, function and workflow the caller is entitled to, as tools it can discover and run. Access is granted per team while we onboard.
One tool call{
"name": "w6w_invoke",
"arguments": {
"ref": "app:io.w6w.2chat#channel-command",
"input": {
"channelUuid": "<channelUuid>",
"command": "<command>"
}
}
}
Every tool names its target with a single ref. The
app: form above doesn't name a connection at all — the
host resolves which of the caller's 2Chat connections to sign
with, and refuses rather than guesses when the answer is ambiguous.
The token is attached host-side, at the moment of the call. It is never a tool argument, never in the model's context, and never in a transcript — so a prompt injection has nothing to exfiltrate.
Tools are derived per end user from what that person has actually connected and is entitled to — not one shared bot identity carrying the union of everyone's access.
Multi-step work runs on the workflow engine and returns a run handle the agent can poll — retries, branching and state survive the conversation that started them.
2Chat's declared health checks are on the surface too, so an agent can tell "the vendor is down" from "your credential expired" before it burns a retry on either.
The MCP surface is part of the hosted platform. 2Chat itself is MIT, and the runtime that executes it is source-available (FSL).
2Chat declares its own checks, so its health is a property of the app rather than something the host guesses at.
Unsigned GET /open/info. A schema-correct 'Not authenticated' / 'Invalid API Key' refusal proves the API gateway is up. 2Chat publishes no status page.
Requests and number-checks used against the plan's maximum, from GET /info. Polling spends one credit.