Documentation menu

Start here

Core concepts

Five identifiers and one state machine. Understand these and the rest of the API reads itself.

clientId — a session

A clientId names one linked WhatsApp number. You choose it (acmemain, ws8f3a2, a customer id…) and never change it.Letters and digits only — no hyphens, underscores, dots or spaces; /start rejects anything else with 400. The reason is chatRoomId, below.

LIDs vs phone numbers

WhatsApp no longer addresses people by phone number internally; it uses a LID (linked id), a long number like123456789012345. Inbound events give you the sender's LID as clientLid (and their phone as remotePhone when WhatsApp reveals it). When you send:

You pass as toWhat happens
123456789012345 (digits, no +)Treated as a LID. Use the clientLid from an inbound event or /chatrooms.
+60123456789Treated as a phone number and resolved to the contact's LID (via WhatsApp's directory the first time, cached after).
…@lid, …@s.whatsapp.net, …@g.usFull JID, used as-is. Groups are always @g.us.

Do not drop the +

A bare phone number without + is interpreted as a LID and the send will go nowhere. Use /check-owner-lid to convert numbers to LIDs when you need them stored.

chatRoomId — a conversation

Every conversation has a deterministic, self-describing key:

<clientId>-<myLid>-<clientLid>
acmemain-60111111111111-123456789012345          # 1:1 chat
acmemain-60111111111111-120363012345678901@g.us  # group

myLid is the linked number's own LID. Because clientId has no symbols and LIDs are digits, the three parts always split unambiguously — and the same phone paired under two different clientIds keeps separate conversations. You get it on every event and never need to build it, but you can.

messageId

WhatsApp's own id for a message (3EB0A1B2C3D4E5F6-style). Returned by every send, carried on every event, and used for replies (quotedMessageId), reactions, edits, deletes and read receipts. It is unique within (clientId, chatRoomId) — the triple is your de-duplication key for webhook retries.

One exception: the media library endpoints (/media-files…) use the storage record's UUID instead. Do not mix the two.

Session lifecycle

POST /start ──► initializing ──► qr (scan needed) ──► connected
                          └──► connected (already paired)
any time: logged_out (device removed on the phone)  |  error (connect failed)
statusMeaningWhat you do
initializingConnectingPoll GET /qr every ~2 s
qrNeeds pairing; qr holds a PNG data-URLShow it; keep polling
connectedPaired and onlineSend. (Optionally /set-org-id for hosted history.)
logged_outUser unlinked the devicePOST /start again → new QR
errorConnect failedRetry /start; escalate if persistent

Once connected, the platform owns reconnection: sessions resume automatically after every deploy or crash and a heartbeat re-checks them every two minutes. See Reliability.

orgId — hosted history

Optional. A workspace id you choose, set once via /set-org-id. Until it is set, the platform stores no history for that session (it only relays). With it set, and the webhook pointed at the hosted receiver, every message in and out is persisted and readable via the hosted inbox endpoints.

Routing (what podName means)

Sessions are spread across several machines. Any request carrying a clientId — in the query string or the JSON body — is transparently forwarded to the machine holding that session, so you can call one base URL and ignore podName. Two rules:

  • Multipart uploads (/send-media-message) must also carry ?clientId=… in the URL — the router cannot read form bodies.
  • If the owning machine is momentarily down you get a 502; retry with backoff. Sessions resume by themselves.