How to Send WhatsApp Messages with Node.js
Five minutes, three steps: get a key, install the SDK, send a message. No business verification, no message templates, no waiting for anyone to approve anything.
1. Get your credentials
Start a free trial, 14 days and no card, then pair your number by scanning a QR code, the same way you would pair WhatsApp Web.
In wsapi.chat/app/settings, generate an API key. You need two values:
X-Api-Key, your account keyX-Instance-Id, which paired number to send from
Keep both server-side. Never ship them in browser code.
export WSAPI_API_KEY="your_api_key" export WSAPI_INSTANCE_ID="your_instance_id"
2. Send your first message
npm install @wsapichat/client
Node 16 or later.
import { WSApiClientFactory } from "@wsapichat/client";
const client = WSApiClientFactory.create({
apiKey: process.env.WSAPI_API_KEY,
instanceId: process.env.WSAPI_INSTANCE_ID,
});
const result = await client.messages.sendTextAsync({
to: "1234567890@s.whatsapp.net",
text: "Hello from Node.js",
});
console.log("Message ID:", result.id);That is the whole integration. The recipient is the phone number in international format, no + and no spaces, followed by @s.whatsapp.net.
Prefer plain HTTP? There is no SDK requirement:
curl -X POST https://api.wsapi.chat/messages/text \
-H "Content-Type: application/json" \
-H "X-Api-Key: $WSAPI_API_KEY" \
-H "X-Instance-Id: $WSAPI_INSTANCE_ID" \
-d '{"to":"1234567890@s.whatsapp.net","text":"Hello world!"}'A 201 comes back with the message id, which you use later to react, edit, delete or mark as read:
{ "id": "01234567890123456" }3. Send an image
Same shape, different endpoint. Pass a URL or base64 data:
await client.messages.sendImageAsync({
to: "1234567890@s.whatsapp.net",
imageUrl: "https://example.com/cat.jpg",
mimeType: "image/jpeg",
caption: "Cat pic",
});Video, audio, voice notes, documents, stickers, contact cards and locations all work the same way. One endpoint each, one JSON body.
Every method has a try variant that returns a result object instead of throwing, if you would rather branch than catch: trySendTextAsync, trySendImageAsync, and so on.
Receiving messages
Sending is half an integration. For incoming messages you have two options, and neither needs a public URL if you do not want one.
Webhooks. We POST each event to your endpoint.
SSE. You hold an open connection and events stream in. Useful in development, when your laptop has no public address, and in any environment where you would rather not expose an inbound endpoint.
The SDK exposes both. See webhook and SSE event delivery for the event types and payload shapes.
The errors you will actually hit
| Code | What it means |
|---|---|
400 | Malformed body, usually the to field missing the @s.whatsapp.net suffix |
401 | Wrong or missing API key |
409 | Device not paired. Scan the QR again |
503 | Instance not available. It is starting up or reconnecting |
409 is the one that surprises people. A paired session can drop. The instance reconnects on its own, and until it does, sends are rejected rather than silently lost. Handle it as a retry, not as a failure.
Where to go next
- Quickstart. The same thing without the explanation.
- The same walkthrough in Python. Same API, same shapes, different language.
- Node.js SDK reference. Every method and both error-handling styles.
- Send a text message. The raw endpoint.
- Free trial. 14 days, no card. Then $5 a month per instance, with no per-message fees.