WhatsApp API in Node.js: From Setup to First Message (2026)
Send WhatsApp messages with Node.js using the Rapiwa API. Complete tutorial with fetch, axios, Express webhooks, async/await patterns, and error handling. Works in Node 18+.
You can send WhatsApp messages from Node.js using the built-in fetch API (Node 18+) or axios to call the Rapiwa REST API. Connect your WhatsApp number to Rapiwa ($5/month), get an API key, and send your first WhatsApp message in under 5 lines of JavaScript. This tutorial covers basic sending, Express webhooks, async patterns, TypeScript types, and production best practices.
Prerequisites
- Node.js 18+ (for native
fetch) - Rapiwa account: free 3-day trial
- API key from Dashboard → API Keys
Step 1: Send Your First Message (Node 18+ — No Libraries)
// send.mjs
const API_KEY = "YOUR_RAPIWA_API_KEY";
const response = await fetch("https://app.rapiwa.com/send-message", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
number: "8801234567890",
message: "Hello from Node.js! 🚀"
})
});
const data = await response.json();
console.log(data);
// { status: 'success', messageId: 'msg_abc123', timestamp: '...' }
Run: node send.mjs
Step 2: Create a Reusable WhatsApp Module
// whatsapp.mjs
const API_KEY = process.env.RAPIWA_API_KEY;
const BASE_URL = "https://app.rapiwa.com";
const headers = {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
};
export async function sendText(phone, message) {
const res = await fetch(`${BASE_URL}/send-message`, {
method: "POST",
headers,
body: JSON.stringify({ number: phone, message })
});
if (!res.ok) throw new Error(`API error: ${res.status} ${await res.text()}`);
return res.json();
}
export async function sendImage(phone, imageUrl, caption = "") {
const payload = { number: phone, image: imageUrl };
if (caption) payload.caption = caption;
const res = await fetch(`${BASE_URL}/send-message`, {
method: "POST", headers,
body: JSON.stringify(payload)
});
return res.json();
}
export async function sendDocument(phone, docUrl, filename, caption = "") {
const res = await fetch(`${BASE_URL}/send-message`, {
method: "POST", headers,
body: JSON.stringify({ number: phone, document: docUrl, filename, caption })
});
return res.json();
}
export async function checkNumber(phone) {
const res = await fetch(`${BASE_URL}/check-number?number=${phone}`, { headers });
return res.json();
}
Usage:
import { sendText, sendImage, sendDocument } from "./whatsapp.mjs";
// Send text
await sendText("8801234567890", "Hello!");
// Send image
await sendImage("8801234567890", "https://example.com/product.jpg", "New product!");
// Send PDF invoice
await sendDocument("8801234567890",
"https://example.com/invoice.pdf",
"Invoice_001.pdf",
"Your invoice is attached."
);
Step 3: Using Axios (CommonJS / Older Node.js)
// If you prefer axios or need CommonJS
const axios = require("axios");
const rapiwa = axios.create({
baseURL: "https://app.rapiwa.com",
headers: { "Authorization": `Bearer ${process.env.RAPIWA_API_KEY}` }
});
async function sendWhatsApp(phone, message) {
const { data } = await rapiwa.post("/send-message", { number: phone, message });
return data;
}
// Error handling with axios
async function safeSend(phone, message) {
try {
const result = await sendWhatsApp(phone, message);
console.log("Sent:", result.messageId);
return result;
} catch (error) {
if (error.response) {
console.error(`API Error ${error.response.status}:`, error.response.data);
} else {
console.error("Network error:", error.message);
}
return null;
}
}
Install: npm install axios
Step 4: Bulk Sending with Delay
// delay helper
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
async function sendBulk(contacts, messageTemplate, delayMs = 1500) {
const results = { success: 0, failed: 0, errors: [] };
for (let i = 0; i < contacts.length; i++) {
const contact = contacts[i];
// Replace {placeholder} with actual values
const message = messageTemplate.replace(
/\{(\w+)\}/g, (_, key) => contact[key] ?? ""
);
try {
const response = await fetch("https://app.rapiwa.com/send-message", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.RAPIWA_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ number: contact.phone, message })
});
const data = await response.json();
if (data.status === "success") {
results.success++;
console.log(`[${i+1}/${contacts.length}] ✓ ${contact.phone}`);
} else {
results.failed++;
results.errors.push({ phone: contact.phone, error: data });
}
} catch (err) {
results.failed++;
results.errors.push({ phone: contact.phone, error: err.message });
}
if (i < contacts.length - 1) await sleep(delayMs);
}
console.log(`Done: ${results.success} sent, ${results.failed} failed`);
return results;
}
// Usage
const contacts = [
{ phone: "8801111111111", name: "Alice", orderId: "12345" },
{ phone: "8802222222222", name: "Bob", orderId: "12346" }
];
await sendBulk(contacts, "Hi {name}! Order #{orderId} is confirmed.", 1500);
Step 5: Express Webhook for Incoming Messages
// webhook-server.mjs
import express from "express";
const app = express();
app.use(express.json());
const API_KEY = process.env.RAPIWA_API_KEY;
// Incoming message webhook
app.post("/webhook/whatsapp", async (req, res) => {
// Always respond 200 immediately
res.json({ status: "ok" });
const { event, from, fromName, message } = req.body;
if (event !== "message.received") return;
console.log(`Message from ${fromName} (${from}): ${message}`);
// Handle keywords
let reply;
const text = message.toLowerCase().trim();
if (text.includes("order") || text.includes("track")) {
reply = "To track your order, visit: https://store.com/track";
} else if (text === "hi" || text === "hello") {
reply = `Hi ${fromName}! 👋 How can I help you today?\n\nReply with:\n*ORDER* - Track your order\n*PRICE* - View pricing\n*HUMAN* - Talk to support`;
} else {
reply = "Thanks for your message! Our team will reply within 1 hour.";
}
// Send reply via Rapiwa
await fetch("https://app.rapiwa.com/send-message", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ number: from, message: reply })
});
});
app.listen(3000, () => console.log("Webhook server on port 3000"));
Install: npm install express
Run: node webhook-server.mjs
Step 6: TypeScript Version
// whatsapp.ts
interface SendMessagePayload {
number: string;
message?: string;
image?: string;
document?: string;
filename?: string;
caption?: string;
}
interface SendMessageResponse {
status: "success" | "error";
messageId?: string;
timestamp?: string;
error?: string;
}
class RapiwaClient {
private readonly headers: HeadersInit;
private readonly baseUrl = "https://app.rapiwa.com";
constructor(apiKey: string) {
this.headers = {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
};
}
async send(payload: SendMessagePayload): Promise<SendMessageResponse> {
const response = await fetch(`${this.baseUrl}/send-message`, {
method: "POST",
headers: this.headers,
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Rapiwa API error: ${response.status}`);
}
return response.json() as Promise<SendMessageResponse>;
}
async sendText(phone: string, message: string): Promise<SendMessageResponse> {
return this.send({ number: phone, message });
}
}
// Usage
const client = new RapiwaClient(process.env.RAPIWA_API_KEY!);
const result = await client.sendText("8801234567890", "Hello from TypeScript!");
console.log(result.messageId);
Environment Variables (Best Practice)
Never hardcode your API key. Use environment variables:
# .env
RAPIWA_API_KEY=your_actual_api_key_here
// Load with dotenv
import "dotenv/config"; // or: require("dotenv").config()
const API_KEY = process.env.RAPIWA_API_KEY;
npm install dotenv
FAQ
Does Node.js have a built-in HTTP client for WhatsApp API?
Yes. Node.js 18+ includes fetch natively. No additional library is needed. For older Node versions, use axios (npm install axios) or node-fetch (npm install node-fetch).
What is the phone number format for Node.js WhatsApp API calls?
Use international format without the + sign: "number": "8801234567890" (country code + number, no spaces or dashes).
Can I receive WhatsApp messages in a Node.js Express app? Yes. Set up a POST endpoint in Express, configure it as a webhook URL in the Rapiwa dashboard. Rapiwa will POST incoming messages to your URL in real time.
Is there a Rapiwa SDK for Node.js / TypeScript?
Not yet — it's on the Rapiwa roadmap. The raw fetch API with TypeScript types (as shown above) provides the same functionality in 10–15 lines.
How do I deploy a Node.js WhatsApp webhook to production? Deploy to any Node.js hosting: Railway, Fly.io, Render, Vercel (serverless), or a VPS. Ensure your URL is public (HTTPS) and accessible from the internet.
