Send WhatsApp Messages from Node.js
The whabuzz npm package is a Node.js client for the WhaBuzz WhatsApp API. It has no dependencies, uses fetch, ships ESM, CommonJS and TypeScript types, and passes Meta's own request and response bodies through unchanged. This post sends a template and a text message, verifies a webhook and handles errors.
What do you need before you start?
You need a WhaBuzz API key, one connected WhatsApp number, and Node 18 or newer. Per the Node.js SDK page, the package targets Node 18+ and edge runtimes. As of 16 Oct 2026 the latest version on npm is 0.1.1, so expect the surface to move as it matures. Pin the version you test against.
npm install whabuzz
export WHABUZZ_API_KEY="wb_live_..." # your keybashThe client reads WHABUZZ_API_KEY from the environment. You can also pass new WhaBuzz({ apiKey }). The constructor takes apiKey, baseUrl, tenant, fetch and timeoutMs, and the default timeout in the SDK source is 30 seconds. Use the sandbox with test keys while you build. Connecting a number is covered in the getting started guide.
How do you send a text message from Node.js?
Call wb.messages.send with Meta's /messages body plus from. Per the sending messages guide, WhaBuzz does not have its own message format, and from is the phone_number_id of one of your numbers or its E.164 number.
import { WhaBuzz } from 'whabuzz';
const wb = new WhaBuzz(); // reads WHABUZZ_API_KEY
const res = await wb.messages.send({
from: '+14692311132', // your connected number
to: process.env.TO, // the recipient, E.164
type: 'text',
text: { body: 'Hello from WhaBuzz' },
});
console.log(res.messages[0].id);jsThe SDK adds messaging_product: 'whatsapp' for you. The response is Meta's, so res.messages[0].id is the wamid. That id is what later message.status events refer to.
A free-form text only works inside the 24-hour customer window, meaning the customer messaged you in the last 24 hours. WhaBuzz does not check the window. Meta decides, and if Meta refuses, you see Meta's error unchanged.
One more behaviour from the docs: a busy number can answer 202 {"status":"queued"}, and the message then goes out in order. Do not assume the response always contains messages[0].
How do you send a template message?
Use type: 'template' and Meta's template object. To start a conversation, WhatsApp requires an approved template. The templates guide covers creating one and waiting for approval. Once it is approved, this is the same shape the getting started guide shows with curl:
const res = await wb.messages.send(
{
from: '+14692311132',
to: process.env.TO,
type: 'template',
template: {
name: 'renewal_reminder',
language: { code: 'en' },
components: [
{
type: 'body',
parameters: [
{ type: 'text', text: 'Kerry' },
{ type: 'text', text: '31 Oct' },
],
},
],
},
},
{ idempotencyKey: 'renewal-48213-2026-10' },
);jsrenewal_reminder is the example template name from the docs. Use the name of a template approved on your own account.
The second argument carries idempotencyKey. It becomes the Idempotency-Key header, so a retry will not send twice. The rules are in Stop duplicate WhatsApp API messages. Make the key from the business event, and store it with your job before the first attempt.
Which methods does the SDK have?
Every method below exists in the package source at sdks/node, and the Node.js SDK page lists the same surface.
| Resource | Methods |
|---|---|
wb.tenant | get |
wb.messages | send, batch, getBatch, listBatchItems, cancelBatch, markRead |
wb.templates | list, create, get |
wb.numbers | list, get |
wb.webhooks.endpoints | list, create, get, update, delete, verify, rotateSecret |
wb.events | list, get, iterate |
wb.media | get |
For any route without a helper, call wb.request({ method, path }). Platform keys that act for many businesses pass tenant in the constructor or per call. See the tenants guide.
How do you handle errors in Node.js?
Failed calls throw WhaBuzzError. It has status, source, code, meta, details, requestId and body. Check source first, because it tells you who produced the error. Per the errors page, meta means Meta answered with an error and WhaBuzz returns it verbatim, and whabuzz means WhaBuzz stopped the request. The SDK adds client for requests that never completed, such as a network failure or timeout.
import { WhaBuzzError } from 'whabuzz';
try {
await wb.messages.send(body, { idempotencyKey: key });
} catch (e) {
if (!(e instanceof WhaBuzzError)) throw e;
if (e.source === 'meta') {
console.error('Meta refused', e.meta.code, e.meta.message, e.requestId);
} else if (e.source === 'client') {
// timeout or connection error: the outcome is unknown, retry with the SAME key
} else {
console.error('WhaBuzz', e.status, e.code, e.requestId);
}
}jsThree rules for the branches:
- Log
requestId. Every response has arequest_id, and you can look it up withGET /v1/requests/{request_id}. - Do not auto-retry Meta 4xx. The errors page says 4xx means your request or account state.
- Retry unknown outcomes with the same key. A
clienterror, or a502 meta_unreachable, means the message may or may not have gone out.
Branch on error.code for Meta errors, for example 131047 when the 24-hour window is closed and you need a template.
How do you verify a webhook in Node.js?
Use verifyWebhook on the exact bytes you received, before any JSON parsing. It checks the X-WhaBuzz-Signature header and returns the parsed event.
import express from 'express';
import { verifyWebhook, WebhookVerificationError } from 'whabuzz';
const app = express();
app.post('/whatsapp/events', express.raw({ type: '*/*' }), async (req, res) => {
try {
const event = await verifyWebhook(req.body, req.headers, process.env.WHABUZZ_WEBHOOK_SECRET);
// store event.dedupe_key first, then process
res.sendStatus(200);
} catch (e) {
if (e instanceof WebhookVerificationError) return res.status(400).send(e.reason);
throw e;
}
});jsexpress.raw keeps the body as a Buffer, which verifyWebhook accepts. The error carries a reason: missing_header, malformed_header, timestamp_out_of_tolerance, signature_mismatch or invalid_json. The timestamp tolerance is 300 seconds.
Verification is the first step, not the last. Deliveries are at least once, so dedupe on dedupe_key as described in WhatsApp webhooks that never lose an event. Express is only an example. The function also runs on Workers, Deno and Bun, per the SDK source.
Frequently asked questions
Does the whabuzz package have dependencies?
No. Per the Node.js SDK page it has zero dependencies and uses fetch.
Which Node versions does it support?
Node 18 and newer, plus edge runtimes.
Does the SDK retry failed requests for me?
The source has no automatic retry. A timeout raises a client error. Retry yourself, with the same idempotencyKey.
Can I use the SDK from TypeScript?
Yes. The package ships full TypeScript types.
Is there a command line tool?
Yes. The same package ships the whabuzz CLI. See the CLI page.
Related reading
- Node.js SDK reference and the sending messages guide.
- Stop duplicate WhatsApp API messages for retry-safe sends.
- WhatsApp webhooks that never lose an event for the receiving side.
- All guides by task and the WhatsApp API overview.