Documentation menu

Start here

Quickstart

From zero to a paired number that sends and receives, in five short steps.

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.

Request
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"
  }'
Response
{ "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.

Request
# poll every ~2 s until status is "connected"
curl "https://api.meowsapp.com/qr?clientId=acmemain" -H "Authorization: Bearer $SN_TOKEN"
Responses over time
{ "clientId": "acmemain", "status": "qr", "qr": "data:image/png;base64,iVBORw0KGgo…" }

{ "clientId": "acmemain", "status": "connected", "qr": "" }
Browser
// 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 number

Pairing 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.

Request
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 👋" }'
Response
{ "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:

Minimal receiver (Express)
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:

Reply from your handler
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 to is a LID and what a chatRoomId is.
  • 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.