1. Get a token
Access is by account token. Request access and you will receive a bearer token forhttps://api.meowsapp.com. Keep it server-side; it grants full control of every session on your account. Export it for the examples below:
export SN_TOKEN="…your token…"2. Start a session
Pick a clientId — letters and digits only, chosen once, kept forever (your customer or workspace id is a good choice). Pass the webhook that should receive messages in the same call, so the session never exists without one.
curl -X POST "https://api.meowsapp.com/start?clientId=acmemain" \
-H "Authorization: Bearer $SN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://example.com/whatsapp/incoming",
"webhookToken": "9f3b…a-long-random-secret-you-generate"
}'{ "clientId": "acmemain", "status": "initializing", "podName": "node-1",
"webhookUrl": "https://example.com/whatsapp/incoming", "pollLookupUrl": "" }3. Pair the number
Poll GET /qr. While status is qr, show the PNG; the user scans it from WhatsApp → Settings → Linked devices → Link a device.
# poll every ~2 s until status is "connected"
curl "https://api.meowsapp.com/qr?clientId=acmemain" -H "Authorization: Bearer $SN_TOKEN"{ "clientId": "acmemain", "status": "qr", "qr": "data:image/png;base64,iVBORw0KGgo…" }
…
{ "clientId": "acmemain", "status": "connected", "qr": "" }// Render the data-URL directly:
document.querySelector("img#qr").src = res.qr;Prefer a code the user types instead of a camera scan? Call /start first, then:
curl -X POST "https://api.meowsapp.com/pair-phone" \
-H "Authorization: Bearer $SN_TOKEN" -H "Content-Type: application/json" \
-d '{ "clientId": "acmemain", "phone": "60123456789" }'
# → { "code": "ABCD-EFGH" } type it under Linked devices → Link with phone numberPairing is one-time
Once paired, the session stays linked across restarts and deploys. Calling /start again with the sameclientId just reconnects — no QR. Only /disconnect or the user removing the device on their phone unpairs it.
4. Send a message
to accepts a phone number with +, a LID (WhatsApp's internal user id, which you will see on inbound events asclientLid), or a full JID. Phone numbers are resolved to the contact's LID automatically.
curl -X POST "https://api.meowsapp.com/send-message" \
-H "Authorization: Bearer $SN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "clientId": "acmemain", "to": "+60123456789", "body": "Hello from the API 👋" }'{ "status": "sent", "messageId": "3EB0A1B2C3D4E5F6" }5. Receive a reply
Reply from the phone. Your webhook receives one JSON event per message, authenticated with the webhookToken you chose:
import express from "express";
const app = express();
app.use(express.json({ limit: "2mb" }));
app.post("/whatsapp/incoming", (req, res) => {
if (req.get("authorization") !== `Bearer ${process.env.WEBHOOK_TOKEN}`) return res.sendStatus(401);
const ev = req.body; // one event per call
const key = `${ev.clientId}:${ev.chatRoomId}:${ev.messageId}`; // de-duplicate on this
if (ev.type === "text" && !ev.fromMe) {
console.log(`${ev.remoteName ?? ev.pushName} (${ev.clientLid}): ${ev.body}`);
// reply: POST /send-message with to = ev.clientLid, quotedMessageId = ev.messageId
}
res.sendStatus(200); // ack fast; do heavy work async
});
app.listen(3000);…and answering from code closes the loop:
const res = await fetch("https://api.meowsapp.com/send-message", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SN_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ clientId: "acmemain", to: ev.clientLid, body: "Thanks — got it!", quotedMessageId: ev.messageId }),
});
const { status, messageId } = await res.json();Respond 2xx within 15 seconds
Non-2xx or slow responses are retried (4 attempts, exponential backoff) and then dropped. Acknowledge immediately and process asynchronously. Retries mean you may see an event twice — de-duplicate on (clientId, chatRoomId, messageId).
Next steps
- Core concepts — why
tois a LID and what achatRoomIdis. - Receiving messages — every event type (media, polls, buttons, reactions, edits, groups).
- Sending messages — media, polls, buttons, replies, mentions, typing indicators.
- Hosted inbox — if you would rather not run your own message store.