Developers
A WhatsApp API and a webhook for your own backend
MultiChats exposes two integration points. A REST API lets your code send messages from any connected number with an API key, and a webhook delivers every inbound message and every reply your agents send to an endpoint you control — so your CRM, bot or bookkeeping service reacts to conversations without polling anything or scraping a screen.
Authentication
One header. The key is server-to-server — it carries no user identity and must never be shipped to a browser.
Base URL
https://api.multichats.com/apiHeader
X-Api-Key: <your key>Errors
Real HTTP status codes. 409 means the number is reconnecting, 404 is often a normal answer on lookup routes, and 400 carries a validation message.
curl -X POST https://api.multichats.com/api/accounts/<accountId>/messages \
-H "X-Api-Key: $MULTICHATS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "233240000001@c.us",
"type": "text",
"text": "Your order #4182 has shipped."
}'The send is queued and executed by the server that currently owns that number, which is what keeps one number from being driven by two machines at once. You get a queue id back immediately and a delivery receipt when it has actually gone out — so a slow or reconnecting session delays a message instead of losing it.
Endpoint reference
Every route an API key can call. Paths are relative to the base URL above; an account may be addressed by its UUID or by its gateway instance id.
Accounts
An account is one connected WhatsApp number. Routes that need the live session (QR, logout, reconnect) are proxied to the channel engine that currently owns it; the rest read and write this backend's own rows. An account may be addressed by its UUID or by its gateway instance id.
| Method | Path | What it does |
|---|---|---|
| GET | /accounts | List connected numbers, optionally filtered by workspace. |
| GET | /accounts/:id | One account plus a live QR when it is waiting to be linked. The QR rotates roughly every 20 s and is never stored. |
| POST | /accounts | Register a number. `gatewayId` picks which region will hold the session. |
| DELETE | /accounts/:id | Log out upstream, then delete the row, its history and its stored session. |
| POST | /accounts/:id/qr | Ask the live session for a fresh pairing code. |
| POST | /accounts/:id/reconnect | Restart the session without unlinking the number. |
| POST | /accounts/:id/logout | Unlink the number from WhatsApp. Re-linking needs a new QR scan. |
| POST | /accounts/:id/backfill | Pull recent history for the account's chats. |
| POST | /accounts/:id/read-all | Mark every conversation on the number as read. |
| PUT | /accounts/:id/gateway | Move the session to another region. Brief offline window, no new QR. |
| POST | /accounts/:id/label | Rename the account as it appears in the console. |
| POST | /accounts/:id/send-interval | Pacing between outbound messages for this number. |
| POST | /accounts/:id/auto-reject-calls | Automatically decline incoming WhatsApp calls. |
| POST | /accounts/:id/read-receipts | Whether the number sends read receipts. |
| GET | /accounts/:id/events | Operations log for the number — status changes, QR issued, scanned, reconnect, logout, errors. |
| GET | /gateways/options | The regions a new account may be placed in. |
Messages
Sending is asynchronous: the message is queued here and executed by the server that currently owns the number, which is what stops one number being driven by two machines. You get a queue id immediately and a delivery receipt on the webhook.
| Method | Path | What it does |
|---|---|---|
| POST | /accounts/:id/messages | Send text, media, a voice note, a location or a contact card. |
| POST | /accounts/:id/messages/broadcast | One text to many recipients — one queue row each, at bulk priority. |
| GET | /chats/:chatId/messages | History for a conversation, keyset-paginated; filter by query text or message type. |
| GET | /accounts/:id/messages | Keyword search across the number's messages. |
| GET | /accounts/:id/changes | Replay feed. Everything written since a cursor, including edits, acks and deletions — how a client catches up after being offline. |
| POST | /accounts/:id/messages/revoke | Delete a sent message for everyone. |
| POST | /accounts/:id/messages/edit | Edit a message already sent. |
| POST | /accounts/:id/messages/react | Add or clear an emoji reaction. |
| POST | /accounts/:id/messages/forward | Forward a message to another chat. |
| POST | /accounts/:id/messages/star | Star or unstar a message. |
| POST | /accounts/:id/messages/pin | Pin or unpin a message in its chat. |
| GET | /accounts/:id/messages/outbound | The send queue: what is waiting, sending, sent or failed. |
| GET | /accounts/:id/messages/outbound/stats | Queue counts per status. |
| POST | /accounts/:id/messages/outbound/resend | Requeue failed or expired sends. |
| POST | /accounts/:id/messages/outbound/clear | Drop queued, failed or expired rows. Delivered sends are never cleared. |
Chats
Conversation rows live in this backend, so listing and searching them never touches WhatsApp. Chat state that WhatsApp itself owns — read, archive, pin, mute, typing — is proxied to the live session.
| Method | Path | What it does |
|---|---|---|
| GET | /accounts/:id/chats | Conversations by recency, each with a truncated last-message preview. |
| GET | /accounts/:id/chats/lookup | Find a chat by its WhatsApp id. A 404 here is a normal answer, not an error. |
| POST | /accounts/:id/chats/open | Start a conversation with a number that has never written to you. |
| POST | /accounts/:id/chats/:chatId/read | Mark a conversation read. |
| POST | /accounts/:id/chats/:chatId/unread | Mark it unread again. |
| POST | /accounts/:id/chats/:chatId/archive | Archive or unarchive. |
| POST | /accounts/:id/chats/:chatId/pin | Pin or unpin the conversation. |
| POST | /accounts/:id/chats/:chatId/mute | Mute or unmute notifications. |
| POST | /accounts/:id/chats/:chatId/typing | Show the typing indicator to the other side. |
| POST | /accounts/:id/chats/:chatId/sync-history | Ask WhatsApp for older messages in this conversation. |
| DELETE | /accounts/:id/chats/:chatId | Delete the conversation and its history. |
| POST | /chats/:chatId/assign | Assign the conversation to a staff member. |
| POST | /chats/:chatId/note | Attach an internal note. Never sent to the customer. |
| POST | /chats/:chatId/tags | Set the conversation's tags. |
Contacts and groups
WhatsApp now identifies the same person by two different ids — a phone-based one and an opaque one — and they never string-match. These routes resolve between them, so a person is one contact rather than two.
| Method | Path | What it does |
|---|---|---|
| GET | /accounts/:id/contacts | Known contacts for the number, with both identities where resolved. |
| GET | /accounts/:id/contacts/lookup | One contact by WhatsApp id. |
| GET | /accounts/:id/contacts/groups | Which groups a person belongs to — matched on either identity. |
| GET | /accounts/:id/groups | The groups this number is a member of. |
| GET | /accounts/:id/chats/:chatId/members | A group's current roster. |
| POST | /accounts/:id/chats/:chatId/group/subject | Rename a group. |
| POST | /accounts/:id/chats/:chatId/group/description | Set the group description. |
| POST | /accounts/:id/chats/:chatId/group/participants | Add, remove, promote or demote members. |
| POST | /accounts/:id/chats/:chatId/group/invite | Get the group's invite link. |
| POST | /accounts/:id/chats/:chatId/group/invite/revoke | Revoke the invite link. |
| POST | /accounts/:id/chats/:chatId/group/leave | Leave the group. |
| GET | /contacts/book | Address book across every number, one row per person. |
| GET | /contact-names | The shared name book. |
| PUT | /contact-names/:key | Rename a person once, everywhere. |
Media
Media bytes live in object storage in the region that received them; the API hands out the metadata and a short-lived signed token. Files are content-addressed and immutable, so they cache indefinitely.
| Method | Path | What it does |
|---|---|---|
| GET | /media/token | Mint a short-lived token an <img> or <video> tag can carry. |
| GET | /media/:id | Fetch the file. `variant=thumb` returns the thumbnail where one exists. |
Workspaces and templates
A workspace owns numbers, staff and the settings that apply to all of them — including where the webhook points.
| Method | Path | What it does |
|---|---|---|
| GET | /workspaces | Workspaces the caller can see. |
| POST | /workspaces | Create a workspace. |
| POST | /workspaces/join | Join one with an invite code. |
| GET | /workspaces/:id/members | The roster. |
| POST | /workspaces/:id/invite/rotate | Issue a fresh invite code and retire the old one. |
| POST | /workspaces/:id/webhook | Where message events are delivered. |
| POST | /workspaces/:id/webhook/test | Fire one probe at the saved URL. |
| GET | /workspaces/:id/accounts | Numbers owned by the workspace. |
| POST | /workspaces/:id/accounts/:accountId/assignees | Which staff may work a number. |
| GET | /templates | Canned replies for the workspace. |
| POST | /templates | Create one. |
| PATCH | /templates/:id | Edit one. |
| DELETE | /templates/:id | Delete one. |
Webhook events
Everything that happens on a number is posted to your endpoint as it happens — you never poll.
POST https://your-backend.example.com/hooks/multichats
X-Webhook-Signature: sha256=<hmac of the exact body bytes>
{
"event": "message.received",
"accountId": "5ea23432-519a-4d38-b377-4ddbfc2ff9ee",
"timestamp": "2026-08-14T09:12:44.031Z",
"data": { "from": "233240000001@c.us", "type": "chat", "body": "is it ready?" }
}| Event | Fired when |
|---|---|
message.received | A customer sent a message to one of your numbers. |
message.sent | One of your numbers sent a message — from the console or the API. |
message.ack | A message you sent was delivered, or read. |
message.revoked | A message was deleted for everyone. |
outbound.status | A queued send finished: executed, failed or expired. |
account.status | A number connected, disconnected or needs re-linking. |
account.qr | A new pairing QR was issued for a number. |
group.updated | A group's subject, description or roster changed. |
Durable, not best-effort
Deliveries are written to an outbox in the same transaction that records the message, then retried with backoff. A receiver that is down for ten minutes costs latency, not events.
Signed
Each POST carries an HMAC-SHA256 signature over the exact body bytes, so your endpoint can verify it came from your deployment before acting on it.
Both directions
Inbound customer messages and outbound agent replies both arrive, with the operator attributed — so your system sees the whole conversation, not half of it.
What it is built on
A FastAPI service backed by PostgreSQL and a Redis-compatible cache, with one or more regional channel engines driving the WhatsApp sessions and object storage holding media. Everything durable — messages, chats, contacts, the send queue — lives in your database, and the consoles are static bundles that talk to this API over HTTPS and a websocket.