Connecting a Device
Create a WUTS device, pair it by QR code or pair code, check its state, reconnect after a restart, and disconnect.
A device is a WhatsApp session managed by WUTS on behalf of your tenant. Before you can send or receive messages, you create a device record and pair it with a real WhatsApp account on a phone. This guide walks the full lifecycle.
Every request below is authenticated with your tenant token (Authorization: Bearer <token>).
See Authentication for how to obtain it.
Device lifecycle at a glance
- Create a logical device —
POST /devices. - Pair it with WhatsApp — scan a QR (
GET /qr), type a pair code (POST /paircode), or approve with a passkey (POST /passkey/response+POST /passkey/confirmation). - Verify the session is live —
GET /status. - Reconnect later without re-pairing —
POST /connect. - Disconnect when you are done —
POST /disconnect.
1. Create a device
POST /devices registers a new device for your tenant. The only field is a human-readable
name. Creating a device only registers it — the session is not active until you complete
pairing, so a fresh device is returned with connected: false.
curl -X POST "https://api.wuts.com.br/devices" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name":"WhatsApp Loja 01"}'{
"success": true,
"device": {
"id": "4d0add2c-fdd1-4ee0-9d33-6a8898ce30e3",
"name": "WhatsApp Loja 01",
"connected": false,
"created_at": "2025-03-02T14:15:17Z"
}
}Keep the returned id. The pairing, status, connect, and QR-status endpoints accept it as a
device_id so you can target a specific device when your tenant has more than one.
2a. Pair by scanning a QR code
GET /qr generates a QR for the device, starts connecting in the background, and caches the
payload for polling. It returns both a raw qr_code string and a base64 PNG image.
curl -X GET "https://api.wuts.com.br/qr" \
-H "Authorization: Bearer <token>"{
"success": true,
"data": {
"qr_code": "AAEBEiFo...",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}
}Render image and have the user scan it from their phone:
WhatsApp → Settings → Linked devices → Link a device.
QR codes expire quickly (around 30 seconds). Render and scan immediately. If GET /qr returns
a 504/timeout, simply request a fresh one and retry.
Poll the QR status
While the user scans, poll GET /qr/status to learn whether the cached QR is still valid. Pass
the device_id of the device you are pairing.
curl -X GET "https://api.wuts.com.br/qr/status?device_id=4d0add2c-fdd1-4ee0-9d33-6a8898ce30e3" \
-H "Authorization: Bearer <token>"{
"success": true,
"data": {
"exists": true,
"generated_at": "2025-03-02T13:55:12Z"
}
}When the QR has expired (or was never generated for that device_id), exists is false.
After a successful scan, confirm pairing with GET /status.
If the device is already paired and connected, GET /qr returns 400 already connected —
disconnect first to generate a new QR.
2b. Pair with a pair code
If scanning is impractical, use POST /paircode. WUTS asks WhatsApp for a code the user types
on their phone under Settings → Linked devices → Link a device → Link with phone number
instead. Send the WhatsApp number in international format (digits only; +, spaces, and
punctuation are stripped automatically).
curl -X POST "https://api.wuts.com.br/paircode" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"phone":"5511999999999","device_id":"4d0add2c-fdd1-4ee0-9d33-6a8898ce30e3"}'{
"success": true,
"data": {
"code": "ABCD-1234",
"phone": "5511999999999",
"expires_in": 300,
"cached": true,
"instructions": "Digite este código no WhatsApp: ..."
}
}The code is valid for expires_in seconds (5 minutes), and instructions carries a
ready-to-show hint. A repeated call returns the same code with cached: true while it is still
alive. To force a new code, send the X-WUTS-Refresh-Paircode: true header or add
?refresh=true.
| Error | Meaning |
|---|---|
400 phone number is required | The phone field was missing. |
400 invalid phone number format | Phone must be 10–15 digits, country code included. |
400 already connected | Device is already paired and live — disconnect first. |
device already paired | Stored credentials already exist; use POST /connect instead. |
QR vs pair code
Both methods produce the same paired session — pick whichever fits your UX:
- QR code — fastest when the user has the screen and phone side by side. Short ~30s window, so render it on demand.
- Pair code — better for remote setups, phone screenshots, or when the customer cannot scan. Longer 5-minute window and copy-pasteable. Show the QR first, the pair code as fallback.
2c. Pair with a passkey (WebAuthn)
WhatsApp is rolling out passkey-based device linking: instead of scanning a QR, the account owner approves the link with the passkey stored in their password manager. When WhatsApp offers this path during pairing, WUTS relays the whole handshake through webhooks and two endpoints — your app only needs to run the WebAuthn ceremony in the account owner's browser.
The flow, end to end:
-
Start pairing normally (
GET /qrorPOST /paircode) with your webhook configured. -
WhatsApp offers the passkey path → your webhook receives
pair.passkey_request:{ "public_key": { "challenge": "3q2-7w", "timeout": 60000, "rpId": "web.whatsapp.com", "allowCredentials": [{ "id": "nGViU9nFzQmp7VpAkFyxWQ", "type": "public-key" }], "userVerification": "required" } } -
Run the ceremony in the account owner's browser — the payload is already in the shape
navigator.credentials.getexpects (binary fields are base64url without padding; decode them intoArrayBuffers):const credential = await navigator.credentials.get({ publicKey: decodedPublicKey }); -
Send the credential back with
POST /passkey/response:curl -X POST "https://api.wuts.com.br/passkey/response" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"device_id":"4d0add2c-fdd1-4ee0-9d33-6a8898ce30e3","response":{...credential JSON...}}' -
Your webhook receives
pair.passkey_confirmationwith a short verification code:{ "code": "123-456", "skip_handoff_ux": false }Unless
skip_handoff_uxis true, show the code to the user and ask them to confirm it matches the one on their phone. -
Finish the handshake with
POST /passkey/confirmation(body optional; passdevice_idto target a specific device). Pairing completes andpair.successfires as usual.
If anything goes wrong while WhatsApp processes the passkey notifications, the webhook
pair.passkey_error delivers { "error": "...", "continuation": true|false } —
continuation: true means the failure happened after your credential was accepted, at the
confirmation stage.
| Error | Meaning |
|---|---|
400 response.* is required | The WebAuthn credential is incomplete — send the object exactly as returned by navigator.credentials.get. |
400 device already paired | Stored credentials already exist; use POST /connect instead. |
409 no active pairing session | The pairing socket is not connected — restart pairing via GET /qr or POST /paircode and wait for pair.passkey_request. |
Existing webhook subscriptions are backfilled with the three pair.passkey_* events
automatically; new webhooks include them by default.
3. Check the connection state
GET /status reports the live state of the device. Pass device_id to target a specific one.
curl -X GET "https://api.wuts.com.br/status?device_id=4d0add2c-fdd1-4ee0-9d33-6a8898ce30e3" \
-H "Authorization: Bearer <token>"{
"success": true,
"data": {
"connected": true,
"status": "connected",
"device_id": "4d0add2c-fdd1-4ee0-9d33-6a8898ce30e3",
"jid": "5511999999999@s.whatsapp.net",
"phone_number": "5511999999999",
"is_logged_in": true
}
}status is one of: connected (online and logged in), connecting (socket up, login still
settling), or disconnected. Treat connected: true as the signal that the device is ready
to send and receive messages.
4. Reconnect after a restart
After a gateway or host restart an in-memory session can drop while the stored WhatsApp
credentials remain valid. POST /connect reloads those credentials and reconnects without a
new QR scan — the recommended self-heal primitive (GET /qr would refuse an already-paired
device).
curl -X POST "https://api.wuts.com.br/connect?device_id=4d0add2c-fdd1-4ee0-9d33-6a8898ce30e3" \
-H "Authorization: Bearer <token>"| Response | Meaning | What to do |
|---|---|---|
200 status: connected, needs_qr: false | Reconnected (or already live). | Nothing — device is ready. |
200 status: logged_out, needs_qr: true | Credentials are gone. | Re-pair via GET /qr or POST /paircode. |
503 | Transient failure. | Retry with backoff. |
404 | Device not found or not owned by this tenant. | Verify the device_id. |
5. Disconnect
POST /disconnect tears down the live session, removes the in-memory client, and fires the
device.disconnected webhook. No body is required.
curl -X POST "https://api.wuts.com.br/disconnect" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json"{
"success": true,
"data": {
"message": "device disconnected successfully",
"device_id": "4d0add2c-fdd1-4ee0-9d33-6a8898ce30e3"
}
}A 404 device not found or not connected means the session is already down. After
disconnecting you can pair again with a fresh GET /qr or POST /paircode, or — if the stored
credentials survive — bring it back with POST /connect. The device record itself is not
deleted; it stays listed under GET /devices and can be reconnected later.
Multiple devices per tenant
A single tenant can run several devices at once (for example, one WhatsApp number per store).
Create each with POST /devices, then pass its device_id to the pairing, status, connect,
and disconnect calls to act on exactly the device you mean. GET /devices lists every device
your tenant owns with each one's connected flag and whatsapp_jid.
Devices are isolated per tenant. A token can only operate on devices owned by its own tenant;
targeting another tenant's device_id is rejected. See Conventions for shared
request and response rules.
Next steps
- Receiving messages — wire up inbound events.
- Webhooks — subscribe to
device.disconnectedand message events. - Errors and Rate limits — handle failures gracefully.