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:emailso 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.dev2 · 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-3de5ad5680d4Building 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
}| Field | Type | Notes |
|---|---|---|
qr_png_base64 | string | Bare base64 of a PNG. No data: prefix — add your own. |
expires_at | number | Unix 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.pngScan 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:
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 (sent →
delivered → read), 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
- Wire up a webhook so you stop polling: Concepts → Webhooks.
- Verify webhook signatures: Webhooks → Signature verification.
- Explore the full REST surface: API reference → Channels.