Manual validation with a real WhatsApp account
Automated tests use the mock provider. Behaviour against real WhatsApp servers (QR pairing, encryption, receipts, restrictions) cannot be tested automatically and must be validated by hand before each release that changes the Baileys adapter, the session manager, or the Baileys version.
Use a dedicated test SIM/number that you own and a second phone as the recipient. Never use a customer's number.
Setup
- Run the stack (locally or on staging) with
WHATSAPP_PROVIDER=baileys,BAILEYS_LOG_LEVEL=warn,LOG_LEVEL=info. - Create an organization, project and API key (scopes
instances:*,messages:*,media:*,webhooks:*,events:read). - Register a webhook pointing to a receiver you can watch (e.g. the example receiver:
WEBHOOK_PORT=4999 pnpm --filter @whatstig/example-node startbehind a tunnel, or any request bin you control).
Checklist
Record pass/fail, time and the instance/message IDs for each step.
| # | Step | Expected |
|---|---|---|
| 1 | Create instance, POST /connect {"method":"qr"}, show qrDataUrl | Status QR_REQUIRED; instance.qr_required webhook |
| 2 | Scan with the test phone (Linked devices → Link a device) | Within seconds: CONNECTED, phoneNumber = test number, instance.connected webhook. Phone lists the linked device |
| 3 | Leave the QR unscanned for 3+ minutes on a second instance | DISCONNECTED / QR_TIMEOUT; no further QR codes generated |
| 4 | Pairing code: new instance, connect {"method":"pairing_code","phoneNumber":"+<test number>"}, enter the code on the phone (Link with phone number instead) | CONNECTED |
| 5 | Send a text to the recipient phone | QUEUED → PROCESSING → SENT; message arrives; DELIVERED when the recipient's phone receives it; READ after opening (if read receipts enabled); matching webhooks; messages.sent usage +1 |
| 6 | Upload a JPEG and a PDF, send both | Arrive with caption / file name; statuses as in 5 |
| 7 | Reply from the recipient phone (text, then an image) | message.received webhooks with sender number and content; listed under direction=inbound; with "Store inbound media" on, GET /v1/media/{id}/download returns the image |
| 8 | Send to a number without WhatsApp | FAILED with RECIPIENT_NOT_ON_WHATSAPP or INVALID_RECIPIENT |
| 9 | Restart the worker container (docker restart …whatsapp-worker) | Within ~30–60 s back to CONNECTED without a QR; messages queued during the restart are sent afterwards |
| 10 | Kill the worker hard (docker kill) while sending a batch of 20 messages | After takeover: no duplicates on the recipient phone; any message caught mid-send is UNKNOWN (or upgraded by a later receipt); the rest are sent |
| 11 | Disable network on the server briefly (or block WhatsApp's hosts) | RECONNECTING, then CONNECTED when restored |
| 12 | On the test phone remove the linked device | LOGGED_OUT / LOGGED_OUT_FROM_PHONE, instance.logged_out webhook, no reconnect loop in the logs, credentials gone (has_credentials = false) |
| 13 | POST /logout on a connected instance | Device disappears from the phone's linked devices; LOGGED_OUT / MANUAL_LOGOUT |
| 14 | DELETE an instance with queued messages | Device unlinked, queued messages FAILED / INSTANCE_DELETED, other instances unaffected |
| 15 | Hosted connect link: create, open on a laptop, scan | Page shows QR then "WhatsApp connected"; reopening the link no longer shows a QR |
| 16 | Logs | No QR payloads, keys or message text in any service log |
Capacity measurement (optional)
To size WORKER_MAX_SESSIONS with real sessions, link N authorized test accounts to one worker and record
process_resident_memory_bytes, nodejs_eventloop_lag_p99_seconds and CPU at idle and while sending, for N = 1, 5,
10, 25. Report these separately from the simulated numbers in Benchmarks. Do not use accounts you are
not authorized to operate.