Skip to main content
The official Keebai SDK for Node.js, TypeScript, and modern runtimes. Saves you from writing your own HTTP layer against Meta Graph API or api.keebai.com/v1.
import { WhatsAppClient } from "@keebai/sdk";

const wa = new WhatsAppClient({
  apiKey: process.env.KEEBAI_API_KEY!,        // kbai_pk_<hex64>
  phoneNumberId: process.env.KEEBAI_PHONE_NUMBER_ID!,
});

await wa.messages.sendText({
  to: "+15551234567",
  body: "Hello from Keebai",
});
That’s it.

Why use it

One client, two modes

Pass apiKey and it talks to Keebai with persistence, dashboards, broadcasts, and normalized webhooks. Pass accessToken and it goes straight to Meta Graph API. Same method surface in both modes.

Full WhatsApp coverage

Text, media (image, video, audio, document, sticker), location, contact cards, reactions, mark-read, interactive (buttons, list, CTA URL, catalog), templates, and broadcasts.

Strict TypeScript

Fully typed: inputs, responses, errors. Autocomplete in VS Code, Cursor, or any LSP-aware IDE. Dual ESM + CJS build with .d.ts.

Retries and timeouts

Automatic exponential backoff on 429, 5xx, and network errors. Configurable timeout. Normalizes Keebai and Meta errors into the same class.

Installation

npm install @keebai/sdk
# or
pnpm add @keebai/sdk
# or
yarn add @keebai/sdk
Requires Node.js 20+ (uses global fetch, FormData, and Blob). Also works on edge runtimes (Vercel Edge, Cloudflare Workers, Deno, Bun) and modern browsers.

Configuration

Set credentials via environment variables:
export KEEBAI_API_KEY=kbai_pk_<hex64>        # from app.keebai.com → System Settings → API Keys
export KEEBAI_PHONE_NUMBER_ID=<numeric>       # phone_number_id of your WhatsApp Cloud API channel
export KEEBAI_BASE_URL=https://api.keebai.com/v1   # optional; default
Instantiate the client:
import { WhatsAppClient } from "@keebai/sdk";

const wa = new WhatsAppClient({
  apiKey: process.env.KEEBAI_API_KEY!,
  phoneNumberId: process.env.KEEBAI_PHONE_NUMBER_ID!,
});

The two modes

ModeCredentialBackendWhen
keebai (default)apiKey: kbai_pk_...https://api.keebai.com/v1You’re on the Keebai platform. Persistence, multi-tenant, broadcasts, webhooks, dashboards.
metaaccessToken: EAA...https://graph.facebook.com/v21.0You have your own WhatsApp Business Account and want to go straight to Meta.
The client infers the mode from the credential. Same method names, same return types, same error model — only the transport changes.
// Direct to Meta
const wa = new WhatsAppClient({
  accessToken: process.env.META_ACCESS_TOKEN!,
  phoneNumberId: process.env.META_PHONE_NUMBER_ID!,
});
Trade-off details in Direct Meta mode.

API surface

client.messages.*

MethodWhat it does
sendTextFree-form text inside the 24h window.
sendImageJPG/PNG by URL or media_id. Optional caption.
sendVideoMP4 with optional caption.
sendAudioAudio. voice: true for PTT (Opus in OGG).
sendDocumentPDF/DOCX/etc. with filename and caption.
sendStickerStatic or animated WebP.
sendLocationMap pin with optional name and address.
sendContactsOne or more vCards.
sendReactionEmoji on a previous message.
markReadMark as read, with optional typing indicator.
sendInteractiveButtonsUp to 3 reply buttons.
sendInteractiveListScrollable list with sections.
sendInteractiveCtaUrlSingle button with an external URL.
sendInteractiveCatalogMessageCatalog message (Commerce).
sendTemplateApproved template with named variables.
sendBulkMass broadcast (Keebai mode).
getBulkStatusStatus of a previous broadcast (Keebai mode).
sendRawEscape hatch — raw Graph API body.

client.media.*

MethodWhat it does
uploadUpload a file. Returns a reusable media_id.
getMetadata (mime, size, sha256, signed URL).
deleteDelete an uploaded media asset.
downloadFetch the binary (as: 'blob' | 'arraybuffer').

client.templates.* (Keebai mode)

MethodWhat it does
listList approved templates.
createCreate and submit to Meta for approval.
updateEdit components or category.

Examples

Send an image with a caption

await wa.messages.sendImage({
  to: "+15551234567",
  image: {
    link: "https://cdn.example.com/banner.png",
    caption: "New collection",
  },
});

Template with named variables

await wa.messages.sendTemplate({
  to: "+15551234567",
  templateName: "order_confirmation",
  language: "en_US",
  variables: {
    name: "Alex",
    order: "ORD-1234",
    amount: "$15,000",
  },
});

Interactive buttons

await wa.messages.sendInteractiveButtons({
  to: "+15551234567",
  bodyText: "Confirm your order?",
  buttons: [
    { id: "confirm", title: "Confirm" },
    { id: "cancel", title: "Cancel" },
  ],
});

Upload media once, send N times

import { readFile } from "node:fs/promises";

const buffer = await readFile("logo.png");
const file = new Blob([buffer], { type: "image/png" });

const { id: mediaId } = await wa.media.upload({
  file,
  fileName: "logo.png",
  type: "image/png",
});

for (const phone of recipients) {
  await wa.messages.sendImage({
    to: phone,
    image: { id: mediaId, caption: "Keebai logo" },
  });
}

Mark as read + typing

await wa.messages.markRead({
  messageId: "wamid.HBgL...",
  typingIndicator: "text",
});

Mass broadcast

const { broadcastId } = await wa.messages.sendBulk({
  templateName: "march_promo",
  language: "en_US",
  campaignName: "March 2026",
  recipients: [
    { to: "+15551234567", variables: { name: "Alex" } },
    { to: "+15557654321", variables: { name: "Sam" } },
  ],
});

const status = await wa.messages.getBulkStatus(broadcastId);
console.log(`${status.sent}/${status.total} sent`);

Error handling

import {
  WhatsAppApiError,
  WhatsAppValidationError,
  WhatsAppTimeoutError,
} from "@keebai/sdk";

try {
  await wa.messages.sendText({ to: "+15551234567", body: "hello" });
} catch (err) {
  if (err instanceof WhatsAppApiError) {
    console.log(err.status, err.code, err.message);
  }
}
Common codes:
CodeStatusCause
UNAUTHORIZED401Token revoked or invalid.
FORBIDDEN403Token doesn’t have the required scope.
CHANNEL_NOT_FOUND404phoneNumberId unknown in your company.
invalid_phone422to is not in E.164 format.
MEDIA_REFERENCE_REQUIRED400Media is missing both link and media_id.
THROTTLED429Hit the rate limit (the SDK retries on its own).
429s, 5xx, and network errors retry automatically with exponential backoff. Configurable:
new WhatsAppClient({
  apiKey: "kbai_pk_...",
  phoneNumberId: "100000000000001",
  timeoutMs: 30_000,
  retries: 2,                              // default
});

Direct Meta mode

When you’d rather go straight to Meta without the platform:
const wa = new WhatsAppClient({
  accessToken: process.env.META_ACCESS_TOKEN!,
  phoneNumberId: process.env.META_PHONE_NUMBER_ID!,
});
Concernkeebai modemeta mode
AuthPAT (kbai_pk_...) from app.keebai.comAccess token (EAA...) from developers.facebook.com
PersistenceYes — conversations, messages, contactsNo — Meta doesn’t store history
Normalized webhooksYes — { event, data, timestamp } envelope with HMAC signatureNo — raw Meta envelope
Broadcasts (sendBulk)YesNot available
Templates CRUDYesNot in this SDK; use the Business Management API
DashboardYesNo
Multi-tenantOne PAT, multiple channelsOne token per WABA
Send messagesFull coverageFull coverage

Helpers

import { buildTemplateSendPayload, normalizePhone } from "@keebai/sdk";

const template = buildTemplateSendPayload({
  name: "welcome",
  language: "en_US",
  body: [{ type: "text", text: "Alex", parameterName: "name" }],
  buttons: [{ subType: "quick_reply", index: 0, payload: "GO" }],
});

const phone = normalizePhone("(555) 123-4567"); // "+15551234567"

Resources

GitHub repo

Source code, issues, and PRs. MIT.

npm: @keebai/sdk

Latest version, changelog, install stats.

Agent Skills

The integrate-keebai-whatsapp skill for AI agents, built on this SDK.

REST API

If you’d rather call the API directly without the SDK.