Send WhatsApp messages with Node.js
Send WhatsApp texts, images and files from your Node.js app, from your own number linked to WbizTool like WhatsApp Web. Use the official wbiztool-client package or the fetch built into Node.js 18 and later.
Free trial: 50 messages in any 30 days, no card.
How it works
Get your API key and link your number
Sign up, then link your WhatsApp number on the WhatsApp numbers page with a QR code or a pairing code, like WhatsApp Web. On Settings → API keys, create a key and copy your Client ID (the API keys guide below shows where). The ID on the number's card (such as ID 678) is your
whatsapp_client.Install the client, or use fetch
Run
npm install wbiztool-clientfor the official client. Or call the API with fetch, which needs no package on Node.js 18 and later.Send the message
POST your credentials, the phone number and the text to
https://wbiztool.com/api/v1/send_msg/. A reply with"status": 1and amsg_idmeans the message is queued on your number.Check that it went out
Pass a
webhookURL to be told when the message is sent or fails, or ask the Message status endpoint with themsg_id.
Node.js code examples
npm install wbiztool-clientconst { WbizToolClient } = require("wbiztool-client");
const client = new WbizToolClient({
clientId: 12345,
apiKey: "YOUR_API_KEY",
whatsappClient: 678,
});
(async () => {
// Errors throw, with the API's response in the error message.
const result = await client.sendMessage({
phone: "9876543210",
countryCode: "91",
message: "Hi Aman, your order #4821 has shipped.",
});
console.log(result);
})().catch(console.error);// Node.js 18+ (built-in fetch). Save as .mjs to use top-level await.
const response = await fetch("https://wbiztool.com/api/v1/send_msg/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: 12345,
api_key: "YOUR_API_KEY",
whatsapp_client: 678,
msg_type: 0,
country_code: "91",
phone: "9876543210",
msg: "Hi Aman, your order #4821 has shipped and will arrive on Thursday.",
}),
});
const result = await response.json(); // read the body even when the HTTP code is 400
if (result.status === 1) {
console.log("Queued with msg_id", result.msg_id);
} else {
console.error("Failed:", result.message);
}{"status": 1, "message": "Created", "msg_id": 9817263}// Node.js 20+ (built-in fetch, FormData and fs.openAsBlob). Save as .mjs to use top-level await.
import { openAsBlob } from "node:fs";
const form = new FormData();
form.append("client_id", "12345");
form.append("api_key", "YOUR_API_KEY");
form.append("whatsapp_client", "678");
form.append("msg_type", "2");
form.append("country_code", "91");
form.append("phone", "9876543210");
form.append("msg", "Your invoice for order #4821 is attached.");
form.append("file_name", "invoice-4821.pdf");
form.append("file", await openAsBlob("./invoice-4821.pdf"), "invoice-4821.pdf");
// Don't set Content-Type yourself; fetch adds the multipart boundary.
const response = await fetch("https://wbiztool.com/api/v1/send_msg/", { method: "POST", body: form });
console.log(await response.json());// Node.js 18+ (built-in fetch). Save as .mjs to use top-level await.
const msgId = 9817263;
const response = await fetch(`https://wbiztool.com/api/v1/message/status/${msgId}/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: 12345,
api_key: "YOUR_API_KEY",
}),
});
const result = await response.json(); // read the body even when the HTTP code is 400
if (result.message === "Unknown message id") {
console.log("No message with this ID in your workspace");
} else if (!("status_text" in result)) {
console.error("Request failed:", result.message ?? "no message in response");
} else if (result.status === 1) {
console.log("Sent");
} else if (result.status === 2) {
console.log("Failed:", result.error);
} else {
console.log("Status:", result.status_text);
}// Node.js 18+ (built-in fetch). Save as .mjs to use top-level await.
const tz = "Asia/Kolkata";
const sendAt = new Date(Date.now() + 2 * 60 * 60 * 1000); // two hours from now
// Format the date and time in the timezone you send, not your server's.
const date = new Intl.DateTimeFormat("en-GB", { timeZone: tz, day: "2-digit", month: "2-digit", year: "numeric" }).format(sendAt); // "24/12/2026"
const time = new Intl.DateTimeFormat("en-GB", { timeZone: tz, hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(sendAt); // "09:00"
const response = await fetch("https://wbiztool.com/api/v1/schedule_msg/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: 12345,
api_key: "YOUR_API_KEY",
whatsapp_client: 678,
msg_type: 0,
country_code: "91",
phone: "9876543210",
msg: "Hi Aman, a reminder that your appointment starts in 30 minutes.",
date,
time,
timezone: tz,
}),
});
const result = await response.json(); // schedule errors come back with HTTP 200 and "status": 0
if (result.status === 1) {
console.log("Scheduled with msg_id", result.msg_id);
} else {
console.error("Failed:", result.message ?? "no message in response");
}const express = require("express");
const app = express();
// WbizTool posts form fields, not JSON.
app.post("/wbiztool-webhook", express.urlencoded({ extended: false }), (req, res) => {
const { msg_id, status } = req.body; // status: SENT, FAILED, CANCELLED or EXPIRED
console.log(msg_id, status);
res.sendStatus(200);
});
app.listen(3000);What you can do
Texts, images and files
Set
msg_typeto 0 for text, 1 for an image or 2 for a file. Give a publicimg_urlorfile_url, or upload the file as multipart form data in a field namedfile.Personal messages from your data
Fill
msgfrom your own records – a name, an order number, a due date. WhatsApp formatting such as *bold* and _italic_ works, up to 3,000 characters.Webhooks for every status
Pass
webhookand your server gets a form-encoded POST such asmsg_id=9817263&status=SENTwhen the message is sent, fails, is cancelled or expires.Schedule for later
POST to
/api/v1/schedule_msg/withdate(dd/mm/yyyy),time(HH:MM) and atimezone. Format the date withIntl.DateTimeFormatin that same timezone, not your server's.Drop messages that come too late
expire_after_secondsmarks a message as expired if it hasn't gone out in time, which suits time-sensitive messages such as delivery ETAs.Recurring reminders
The Reminders API repeats a message on a cron schedule, such as 10:00 on the 1st of every month. Reminders need a paid plan.
Good to know
"status": 1means the message is queued, not delivered. Sent is the final success state; the API doesn't report whether the message was delivered or read.- Send message errors come back with HTTP 400 (429 at the daily limit); Schedule message errors usually come back with HTTP 200 and
"status": 0, so always parse the body and checkstatusrather thanresponse.ok. - The official client's
scheduleMessagesends a field the schedule endpoint doesn't read. Call/api/v1/schedule_msg/with fetch instead, as shown above. - Keep the API key on your server, for example in an environment variable. Anyone who has it can send from your numbers, so never put it in browser JavaScript, a mobile app or code you publish.
- To post to a WhatsApp group instead of a number, see the group messages example: it works on every plan, including the free trial.
- The free trial has 50 messages in any 30 days and 1 number, no card. Paid plans start at $25 a month in the US (your price is on Pricing) and send unlimited messages under fair use: up to 1,000 a day per number.
- Messages go out from your own WhatsApp number, so WhatsApp's own rules apply: message people who expect to hear from you.
Questions
- Is there an official Node.js package for the WbizTool API?
- Yes:
wbiztool-clienton npm (npm install wbiztool-client). ItssendMessagesends a message. For scheduling and status checks, call the endpoints with fetch as shown above. - Can I call the API from the browser?
- Don't: the request carries your API key, and anyone who has it can send from your numbers. Call the API from your Node.js server and keep the key in an environment variable.
- How do I know the message was sent?
- A reply with
"status": 1and amsg_idmeans it's queued. Pass awebhookURL to get a POST withSENT,FAILED,CANCELLEDorEXPIRED, or call the Message status endpoint with themsg_id. Sent is the final state: the API doesn't report delivered or read. - Which phone number format should I send?
- Send the number with its country code (919876543210), or without it (9876543210) together with
country_code(91). Spaces, +, dashes and brackets are removed for you, so +91 98765-43210 works too. - Do I need the WhatsApp Business API or Meta approval?
- No. WbizTool sends from your own WhatsApp or WhatsApp Business number, linked like WhatsApp Web. There's no Meta approval and no message templates to get approved.
- Does it cost per message?
- No. The free trial includes 50 messages in any 30 days. Paid plans send unlimited messages under fair use, up to 1,000 a day per number: Lite $25, Standard $59 and Advanced $149 a month in the US; Pricing shows your country's price.
Read the guides
- Send message API
- Message status API
- Schedule message API
- API keys and Client ID
- Connect your WhatsApp number
Related
- PythonSend WhatsApp messages from Python with the official client or plain requests.
- cURLSend, schedule and track WhatsApp messages with one cURL command.
- n8nSend WhatsApp messages from n8n with the WbizTool community node.
- Order updatesOrder confirmations, shipping updates and new-order alerts for Shopify, WooCommerce and more.
Send your first automated WhatsApp message today
Create an account, scan the QR code and send a message in under five minutes.
Start freePaid plans start at $25 a month in the US. Pricing
