Send a WhatsApp Message with Node.js
Send WhatsApp messages from Node.js with built-in fetch: text, images and documents, error handling and delivery status. No packages needed.
Here's how to send a WhatsApp message from Node.js using the fetch function that's built into Node.js 18 and newer — no npm packages to install. You'll end up with a small module you can use from an Express or Next.js app, a background worker or a one-off script, to send text, images and documents and track delivery.
Check your version with node --version; you need 18 or newer.
Before you start
You need three things from your InstaMessage dashboard. It takes about five minutes:
- A free account. Sign up here — no card needed.
- A connected instance. Create an instance and scan its QR code with WhatsApp on your phone (Settings → Linked devices → Link a device), until its status shows Connected.
-
Your
instance_idandaccess_token. On the instance's page, generate an API credential. The token is shown only once, so copy it somewhere safe straight away.
Keep both out of your code: store them as environment variables, so they never end up in Git. On macOS or Linux:
export INSTAMESSAGE_INSTANCE_ID="your-instance-id"
export INSTAMESSAGE_TOKEN="your-access-token"
On Windows (PowerShell):
$env:INSTAMESSAGE_INSTANCE_ID = "your-instance-id"
$env:INSTAMESSAGE_TOKEN = "your-access-token"
+, spaces or leading
0. For example
+91 98765 43210 becomes 919876543210.
Step 1: Write a small module
Save this as instamessage.mjs. It adds your token to every request and throws an error with the API's
own message when something goes wrong:
const API_URL = "https://www.instamessage.in/api/v1";
const INSTANCE_ID = process.env.INSTAMESSAGE_INSTANCE_ID;
const TOKEN = process.env.INSTAMESSAGE_TOKEN;
async function request(method, path, body) {
const response = await fetch(API_URL + path, {
method,
headers: {
Authorization: "Bearer " + TOKEN,
"Content-Type": "application/json",
Accept: "application/json",
},
body: body ? JSON.stringify(body) : undefined,
// Media files are downloaded before sending, so allow up to 60 seconds.
signal: AbortSignal.timeout(60_000),
});
const data = await response.json().catch(() => ({}));
if (!response.ok || !data.success) {
throw new Error(data.error ?? "Request failed with HTTP " + response.status);
}
return data;
}
// Send a WhatsApp message. Extra fields (type, media_url, file_name) are passed through.
export function sendMessage(to, message, extra = {}) {
return request("POST", "/messages/send", { instance_id: INSTANCE_ID, to, message, ...extra });
}
// Look up a sent message, including its delivery status.
export async function getMessage(messageId) {
const data = await request("GET", "/messages/" + encodeURIComponent(messageId));
return data.message;
}
The .mjs ending lets you use import / export without changing your
package.json.
Step 2: Send your first text message
Create send.mjs with your own WhatsApp number to test it:
import { sendMessage } from "./instamessage.mjs";
const result = await sendMessage("919876543210", "Hello from Node.js! ๐");
console.log("Sent! Message ID:", result.message_id);
node send.mjs
Within a second or two the message lands on the phone. result holds the API's reply, for example
{ success: true, message_id: "3EB0A1B2C3D4E5F6", sent_via: "device", fallback_status: null }.
Step 3: Handle errors
Use try / catch — the error message is the API's explanation, or the network problem:
import { sendMessage } from "./instamessage.mjs";
try {
await sendMessage("919876543210", "Your OTP is 482913. It expires in 10 minutes.");
} catch (error) {
console.error("WhatsApp message failed:", error.message);
}
What usually causes an error:
- HTTP 401 — the access token is missing, wrong or revoked.
- "Instance is not connected" — reconnect the instance from your dashboard.
- "instance_id does not match this token" — the token is for a different instance.
- "You've reached your plan's monthly message limit" — upgrade, or wait for the new month.
- HTTP 429 — more than 30 send requests in a minute; wait and try again.
Step 4: Send an image or a document
Pass a type and a public media_url as extra fields. The text becomes an optional caption:
import { sendMessage } from "./instamessage.mjs";
// An image with a caption (JPG, PNG or WebP, up to 5 MB)
await sendMessage("919876543210", "Just in: our new collection", {
type: "image",
media_url: "https://example.com/images/new-arrival.jpg",
});
// A PDF, with the file name the recipient will see
await sendMessage("919876543210", "Here is your invoice.", {
type: "document",
media_url: "https://example.com/invoices/42.pdf",
file_name: "Invoice-42.pdf",
});
Videos, audio and voice notes work the same way — the media rules list every type and size limit.
Step 5: Check if it was delivered and read
import { getMessage, sendMessage } from "./instamessage.mjs";
const result = await sendMessage("919876543210", "Did you get this?");
const message = await getMessage(result.message_id);
console.log(message.status); // pending, sent, delivered, read or failed
read only appears when the recipient has read receipts turned on. To get delivery and read updates
pushed to your server as they happen, set a webhook on your instance.
Protect your WhatsApp number
WhatsApp can restrict or ban a number that looks like it's sending spam. Only message people who expect to hear from you, keep your volume steady, and avoid sending the same text to large lists. Read the full do's and don'ts before you go live.
Next steps
- See every field, error and limit in the API documentation.
- Receive incoming messages with a webhook, and get delivery updates on your server.
- Compare plans and message limits.