Skip to content
WhatIsUp.dev

Quickstart

Goal: get from "no account" to "WhatsApp message sent" in five steps. We'll use curl here. The same calls work from any HTTP client.

You'll need a real WhatsApp account to scan the QR. Use a secondary number — the gateway opens a WhatsApp Web session on it just like the WhatsApp Web tab in your browser.

1 · Get an API key

Sign in to the dashboard at https://app.whatisup.dev. Three sign-in providers are supported:

  • Email + password — instant, no third party.
  • Google — one-click OAuth.
  • GitHub — one-click OAuth, requests read:user + user:email so we have your name + email for the customer record.

Pick whichever you prefer. If you sign up under one provider and later sign in with a different one using the same email, the dashboard transparently links them so you keep a single customer record.

Then Settings → API keys → Create. Copy the key — it's only shown once. Set it as an env var so the snippets below work as-is:

export WHATISUP_API_KEY=zpk_••••••••
export WHATISUP_API=https://api.whatisup.dev

2 · Create a channel

A channel is one logical WhatsApp connection. Each gets its own QR.

curl -sX POST "$WHATISUP_API/v1/channels" \
  -H "Authorization: Bearer $WHATISUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"primary"}'

You'll get back something like:

{
  "id": "8d653c66-e4ff-43ee-97da-3de5ad5680d4",
  "customer_id": "2696bc9b-2e37-43d9-a33b-5a79f81daeba",
  "name": "primary",
  "phone_number": null,
  "status": "pending",
  "last_seen_at": null,
  "metadata": {},
  "created_at": "2026-05-01T12:34:56.000Z",
  "updated_at": "2026-05-01T12:34:56.000Z"
}

Responds 201. status: "pending" means the gateway hasn't started a session yet, and phone_number stays null until pairing completes. Take note of the id — a UUID v4 — you'll use it everywhere below as CHANNEL_ID:

export CHANNEL_ID=8d653c66-e4ff-43ee-97da-3de5ad5680d4

Building multi-tenant? Pass "issue_scoped_key": true on this call and the response also carries scoped_key.secret — an API key that can only ever reach this one channel. Shown once, never readable again.

3 · Get the QR code

Call the QR endpoint until it returns one. The channel starts a session on first QR fetch.

curl -s "$WHATISUP_API/v1/channels/:id/qr" \
  -H "Authorization: Bearer $WHATISUP_API_KEY"

The response carries exactly two fields:

{
  "qr_png_base64": "iVBORw0KGgoAAAANSUhEUgAA...",
  "expires_at": 1779425257573
}
FieldTypeNotes
qr_png_base64stringBare base64 of a PNG. No data: prefix — add your own.
expires_atnumberUnix epoch milliseconds, not an ISO string.

There is no raw / wire-format string in the response. To view it:

curl -s -H "Authorization: Bearer $WHATISUP_API_KEY" \
  "$WHATISUP_API/v1/channels/$CHANNEL_ID/qr" \
  | jq -r .qr_png_base64 | base64 -d > qr.png && open qr.png

Scan it from WhatsApp → Settings → Linked devices → Link a device.

If the channel has no QR yet you get 409 no_qr — that is the normal "retry in a moment" answer while the session spins up, not an error to abort on.

Prefer not to scan? POST /v1/channels/$CHANNEL_ID/pair-code with {"phone_number":"5511999999999"} returns an 8-character code you type into WhatsApp → Linked devices → Link with phone number instead.

The dashboard UI does this for you in real time over SSE. If you'd rather not deal with QR plumbing yourself, point the dashboard at your gateway and click Pair.

4 · Wait for connected

Poll the channel until status flips to connected:

curl -s "$WHATISUP_API/v1/channels/:id" \
  -H "Authorization: Bearer $WHATISUP_API_KEY"

…or, much better, subscribe to Server-Sent Events and react to channel.connected:

Keep the connection open. Every frame is a NAMED SSE event — listen per name, not on `message`. See the SSE page.
curl -s "$WHATISUP_API/v1/events" \
  -H "Authorization: Bearer $WHATISUP_API_KEY"

5 · Send your first message

curl -sX POST "$WHATISUP_API/v1/channels/:id/messages" \
  -H "Authorization: Bearer $WHATISUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"text","to":"5511999999999@s.whatsapp.net","text":"Hello from WhatIsUp.dev"}'

to is a WhatsApp JID — <digits>@s.whatsapp.net (country code + national number, no +, then @s.whatsapp.net). The gateway returns 202:

{
  "message_id": "3EB0C9A17F2B4D8E1A05",
  "client_ref": null,
  "status": "sent"
}

status is "sent" when the channel was connected and WhatsApp took the message, or "queued" when the channel was mid-recovery and we'll retry it. In both cases you get the same message_id, and a message.sent webhook fires on success.

To follow delivery afterwards, use the message.status webhook (sentdeliveredread), or read GET /v1/messages — the gateway does keep a message history you can page through, including bodies and the webhook deliveries that carried each one.

Where to next