Waaru SDK for Node.js
Connect your server or customer console to the scoped Developer API.
Reviewed
Use the Waaru SDK for Node.js for messages, delivery status, conversations and signed events. You need Node.js 22.14+ and a number-bound API key.
The published version is 1.0.0-beta.4. Beta.5 is unpublished. See coverage for operations that still need REST.
What you need
- Connect your WhatsApp number.
- Follow API setup. Reads can keep Logic Flow. For API replies, select the number in Settings > Developer > Webhooks, choose Use your server and confirm (
EXTERNAL_APImode). - Create its key in API keys. Use
messages:sendandmessages:readfor sends/status,media:readfor attachments andinstance:readfor readiness. Reserve webhook management scopes for callback administrators.
Keys stay bound to one number through rotation, without sender override. Contacts, labels and segments are workspace-wide. Authorize each user and conversation on your server; machine keys lack dashboard permissions.
Server handling replaces Logic Flow and automatic replies. Conversations remain in Inbox; callback failure does not restart Logic Flow. Human ownership blocks sends until Release to API.
1. Install and configure
npm install @waaru/[email protected]Store WAARU_API_KEY in your server secret store. Locally, create a git-ignored .env and replace the placeholder:
WAARU_API_KEY=your_api_keyNever put keys in browser code, NEXT_PUBLIC_, URLs or logs. Save as check-number.mjs. It needs instance:read and sends no message.
import Waaru from "@waaru/sdk";
const waaru = new Waaru(); // reads WAARU_API_KEY
const { instance, scopes } = await waaru.instance.get();
console.log({
status: instance.status,
inboundHandlerMode: instance.inboundHandlerMode,
scopes,
});node --env-file=.env check-number.mjsinstance.get() diagnoses status, handling mode and scopes. Send eligibility is checked separately.
Unversioned installs select latest; @waaru/sdk@beta selects beta. Check npm view @waaru/sdk dist-tags: GitHub source may be unpublished. Pin production versions.
2. Send a reply and read its status
Free-form replies need an open 24-hour service window. Persist an idempotency key before each logical send. Keep its body and key when reconciling uncertainty.
Your server supplies permittedRecipient from authorized data and storedLogicalSendId from this message's saved key; neither is an SDK global.
const accepted = await waaru.messages.sendText(
{ to: permittedRecipient, text: "Thanks. Your enquiry reached the team." },
{ idempotencyKey: storedLogicalSendId },
);
const { message } = await waaru.messages.get(accepted.messageId);202 means queued acceptance. Outbound states are queued, sent, delivered, read and failed; uncertainty stays queued. Failures can include failureCode; inbound uses received.
Outside the window, use an eligible approved template. Find it with templates.list() / templates.get(). Match its WABA, name, language and parameters; see template inputs. Consent, quality, ownership and sending limits still apply.
const acceptedTemplate = await waaru.messages.sendTemplate(
{
to: permittedRecipient,
name: approvedTemplate.name,
language: approvedTemplate.language,
components: approvedTemplateComponents,
},
{ idempotencyKey: storedTemplateSendId },
);Supply the exact approved name, language and parameters. Optional components must match its placeholders. Save a separate key; never reuse the text reply's key for different content.
3. Read conversations in your console
const page = await waaru.conversations.list({ limit: 25 });
const nextPage = page.nextCursor
? await waaru.conversations.list({ limit: 25, after: page.nextCursor })
: null;
const history = await waaru.conversations.messages(conversationId, {
limit: 25,
});
const older = history.nextCursor
? await waaru.conversations.messages(conversationId, {
limit: 25,
before: history.nextCursor,
})
: null;Lists default to newest first. Use after for conversation cursors and before for history. Oldest-first newer messages use after or begin with since. Keep filters fixed, deduplicate message IDs and treat cursors as opaque.
Time filters miss later delivery changes; use callbacks or messages.get(). Beta.4 lacks the REST revision feed. See conversation fields for non-text content.
4. Stream permitted attachments
const response = await waaru.media.download(mediaId);
// Stream response.body to your authorized caller, or cancel it if unused.Authorize the user and ready attachment ID. Consume or cancel the stream before its deadline. Outbound media needs public HTTPS links; uploads are unavailable.
5. Receive signed events
Handle the unsigned activation challenge through webhook setup. It is separate from signed delivery and Test webhook.
import { verifyWebhook } from "@waaru/sdk";
const verified = verifyWebhook({
rawBody, // exact Uint8Array bytes, before JSON parsing
timestamp: request.headers.get("x-waaru-timestamp"),
signature: request.headers.get("x-waaru-signature"),
secret: storedSigningSecret,
});
if (!verified) throw new Error("Invalid webhook signature");Validate the event, expected number and matching body/header event ID. Atomically deduplicate the signed body event ID and enqueue before 2xx; signature verification does not deduplicate. Follow the receiver recipe.
Delivery has a 10-second timeout and 24-hour retry window; 404/410 pause. Rotation overlaps secrets for 24 hours. Deduplicate events and reconcile current state. There is no arbitrary replay.
SDK and REST coverage
| Interface | Current coverage | When to use it |
|---|---|---|
| Published SDK beta.4 | 32 operations and 14 scopes | Use the examples above. |
| REST API | 36 operations and 15 scopes | Use OpenAPI for additional reads. |
| SDK beta.5 candidate | 36 operations and 15 scopes | Await npm publication; inspect source and notes. |
Beta.5 adds messages.changes(), campaigns.list/get/recipients(), template lifecycle filters and webhooks.save({ ..., observeAllOrigins: true }). Use REST until publication.
Beta.5 adds includeArchived to contact lookup and restoreArchived with expectedRevision to contact updates. These options grant no send permission; beta.4 rejects them. Use the contact restoration REST guide.
Beta.4 rejects and omits observeAllOrigins; saving resets it to false in the current API. Preserve it with observeAllOrigins: true on every REST webhook save. See feed recovery and campaign pagination. Existing keys do not gain campaigns:read automatically.
The SDK includes workspace-scoped contacts, labels, static segments, templates.list/get, reports.activity, revisioned webhooks and local verifyWebhook. Check scopes in the coverage matrix; repository main may describe unpublished code.
API keys exclude scheduling, workflow/template authoring, uploads, dynamic segments, key/handler management and human release. MCP uses separate OAuth approvals.
The SDK source is MIT-licensed. That license covers the SDK code; the platform terms apply when you use Waaru.
Troubleshoot without duplicate sends
The SDK has zero automatic retries and a 30-second default deadline. Inspect outcomeUnknown. After timeout, interruption or uncertain HTTP/protocol failure, reconcile before retrying. Reuse the send's saved key and identical body; changed content conflicts. requestId is correlation only.
Correct definitive rejections and follow rate limits. Reconcile persistent queued before a new send. See errors and recovery.
Share redacted diagnostics without secrets or customer data.
Next: Complete API reference or activity reports.
Developer API quickstart
Connect your server with scoped reads, signed observations, or explicit Developer API reply handling for one WhatsApp number.
MCP clients
Connect a remote MCP client through OAuth, review workspace permissions, and distinguish read access from explicitly confirmed WhatsApp actions.