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 to | What happens |
|---|---|
123456789012345 (digits, no +) | Treated as a LID. Use the clientLid from an inbound event or /chatrooms. |
+60123456789 | Treated 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.us | Full 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
[email protected] # groupmyLid 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)| status | Meaning | What you do |
|---|---|---|
initializing | Connecting | Poll GET /qr every ~2 s |
qr | Needs pairing; qr holds a PNG data-URL | Show it; keep polling |
connected | Paired and online | Send. (Optionally /set-org-id for hosted history.) |
logged_out | User unlinked the device | POST /start again → new QR |
error | Connect failed | Retry /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.