Blog/Tutorials

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+.

by Moumina
WhatsApp API in Node.js: From Setup to First Message (2026)

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.