venm-khabri
v1.0.3
Published
Developer-first TypeScript SDK for WhatsApp notifications via OpenWA — session management, message queues, templates, webhooks, and more
Maintainers
Readme
Khabri — WhatsApp API SDK
npm version License: MIT TypeScript
Developer-first TypeScript SDK for WhatsApp notifications via OpenWA.
144+ tests passing · Full TypeScript strict mode · Zero external runtime deps beyond axios/p-queue/zod
Khabri provides a clean, type-safe abstraction over the OpenWA API with built-in support for sessions, messages, queues, notifications, templates, webhooks, retries, and middleware — all designed to work seamlessly in Node.js.
khabri (खबरी) means "informer" or "messenger" in Hindi.
📦 Install
npm install venm-khabri
# or
pnpm add venm-khabri
# or
yarn add venm-khabriRequirements: Node.js 18+, TypeScript 5+ (for type support), an OpenWA instance and API key.
Features
- 📱 Session Management — Create, start, stop, force-kill sessions; QR code and pairing code authentication
- 📤 Send Messages — Text, images, videos, audio, documents, stickers, locations, contacts, templates, replies, forwards, reactions
- 🔔 Notifications — Pre-built notification templates: OTP, welcome, offers, promotions, rewards, reminders, and more
- 📋 Queue System — Priority-based message queue with broadcast, batch, pause/resume, and job lifecycle events
- 🔁 Retry Strategy — Configurable backoff (exponential, fixed, linear), max attempts, and delay
- 🔗 Webhooks — Full CRUD management + HMAC-SHA256 verified receiver with framework adapters (Express, Fastify, Koa, Hono, NestJS)
- ⚡ Events & Hooks — Lifecycle hooks (
beforeSend,afterSend,beforeWebhook, etc.) and event-driven architecture - 👥 Groups — Create, manage participants, update subject/description, invite codes
- 💬 Chats — List, read/unread, delete, typing indicators, message history
- 📝 Template Manager — Register, render, and send WhatsApp message templates
- 🛡️ Full TypeScript — Strict types matching OpenWA API v0.7.5, Zod validation, comprehensive error hierarchy
Installation
# npm
npm install venm-khabri
# pnpm
pnpm add venm-khabri
# yarn
yarn add venm-khabriRequirements
- Node.js 18+
- TypeScript 5+ (for type support)
- An OpenWA instance and API key
Quick Start
All message operations require an active session. Here's the full flow:
import { Khabri } from "venm-khabri";
const khabri = new Khabri({
url: "https://openwa.your-instance.com",
apiKey: "your-api-key",
});
// 1. Create a session
const session = await khabri.sessions.create("my-phone");
// → { id: "abc...", name: "my-phone", status: "created", ... }
// 2. Start the session to get a QR code
const started = await khabri.sessions.start(session.id);
// → { ...status: "qr_ready" }
// 3. Get the QR code (scan with WhatsApp → Linked Devices)
const qr = await khabri.sessions.qr(session.id);
// → { qrCode: "data:image/png;base64,...", status: "qr_ready" }
// 4. Once scanned, session status becomes "ready" — send messages!
const result = await khabri.messages.text(
session.id, // session ID
"[email protected]", // recipient chat ID
"Hello from Khabri!", // message text
);
// → { messageId: "msg_...", timestamp: 1234567890 }Quick Start with Express
import express from "express";
import { Khabri, createExpressHandler } from "venm-khabri";
const khabri = new Khabri({
url: process.env.OPENWA_URL!,
apiKey: process.env.OPENWA_API_KEY!,
});
const app = express();
app.post("/webhook", express.json(), createExpressHandler(khabri.webhooks));
app.listen(4000);All middleware adapters (
createExpressHandler,createFastifyHandler,createKoaHandler,createHonoHandler,NestWebhookService) are exported from the main package entry.
Architecture
┌────────────────────────────────────────────────────────┐
│ Khabri Client │
├────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌───────────┐ │
│ │ Sessions │ │ Messages │ │ Chats │ │ Groups │ │
│ │ Manager │ │ (send) │ │(CRUD) │ │ (CRUD) │ │
│ └──────────┘ └──────────┘ └────────┘ └───────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌───────────┐ │
│ │ Contacts │ │ Status │ │ Queue │ │ Templates │ │
│ │ (CRUD) │ │ (stories)│ │Manager │ │ Manager │ │
│ └──────────┘ └──────────┘ └────────┘ └───────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌───────────┐ │
│ │ Webhooks │ │ Notific. │ │ Hooks │ │ Events │ │
│ │ CRUD+Recv│ │Templates │ │System │ │ Emitter │ │
│ └──────────┘ └──────────┘ └────────┘ └───────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Middleware Pipeline │ │
│ │ beforeSend → send → afterSend │ │
│ │ beforeWebhook → process → afterWebhook │ │
│ └────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘All API calls are session-scoped. You must create and start a session before sending messages.
The Session Lifecycle
created → initializing → qr_ready → authenticating → ready
↓ ↓
(scan QR) disconnected
↓ ↓
authenticating failed
↓
ready// Create
const session = await khabri.sessions.create("my-phone");
// Start (transitions to qr_ready)
await khabri.sessions.start(session.id);
// Get QR code for scanning
const { qrCode } = await khabri.sessions.qr(session.id);
// Alternative: pairing code (WhatsApp beta)
const { pairingCode } = await khabri.sessions.pairingCode(session.id, "628123456789");
// Stop / disconnect
await khabri.sessions.stop(session.id);
// Force-kill a stuck session
await khabri.sessions.forceKill(session.id);
// Delete permanently
await khabri.sessions.delete(session.id);
// Get session overview stats
const stats = await khabri.sessions.stats();
// { total: 3, active: 1, ready: 1, disconnected: 1, byStatus: {...} }Modules
Messages
All message methods require a sessionId as the first parameter. Chat IDs use the format {phone}@c.us (e.g., [email protected]).
// Text
await khabri.messages.text(sessionId, "[email protected]", "Hello!");
// Image with caption
await khabri.messages.image(sessionId, "[email protected]", {
url: "https://example.com/photo.jpg",
caption: "Check this out!",
});
// Video
await khabri.messages.video(sessionId, "[email protected]", {
url: "https://example.com/video.mp4",
caption: "My vacation",
});
// Audio
await khabri.messages.audio(sessionId, "[email protected]", {
url: "https://example.com/audio.mp3",
});
// Document
await khabri.messages.document(sessionId, "[email protected]", {
url: "https://example.com/report.pdf",
filename: "Q3 Report.pdf",
caption: "Quarterly report",
});
// Sticker
await khabri.messages.sticker(sessionId, "[email protected]", {
url: "https://example.com/sticker.webp",
});
// Location
await khabri.messages.location(
sessionId, "[email protected]",
28.6139, 77.2090, // lat, lng
{ description: "New Delhi" },
);
// Contact card
await khabri.messages.contact(
sessionId, "[email protected]",
"Ravi Kumar", "+919876543210",
);
// Template message
await khabri.messages.template(sessionId, "[email protected]", {
templateName: "welcome_msg",
vars: { name: "Anita" },
});
// Reply to a specific message
await khabri.messages.reply(sessionId, "[email protected]", "Got it!", "msg_123");
// Forward a message
await khabri.messages.forward(sessionId, "[email protected]", "msg_123");
// React with emoji
await khabri.messages.react(sessionId, "[email protected]", "msg_123", "👍");
// Delete a message
await khabri.messages.delete(sessionId, "[email protected]", "msg_123");
// Send in bulk
const bulkResult = await khabri.messages.bulk(sessionId, [
{ chatId: "[email protected]", content: { text: "Hello 1" } },
{ chatId: "[email protected]", content: { text: "Hello 2" } },
]);
// Get message history
const history = await khabri.messages.history(sessionId, "[email protected]", {
limit: 50,
includeMedia: false,
});
// Send product
await khabri.messages.product(sessionId, "[email protected]", "product_123", {
catalogId: "catalog_456",
});Notifications
Pre-built notification templates that use the messages API internally:
// OTP verification
await khabri.notify("otp", { phone: "+1234567890", code: "384729", expiry: "5 minutes" });
// Welcome message
await khabri.notify("welcome", { phone: "+1234567890", name: "Anita" });
// Promotional offer
await khabri.notify("offer", {
phone: "+1234567890",
title: "50% Off Weekend Sale",
description: "Use code WEEKEND50",
expiry: "48 hours",
code: "WEEKEND50",
});
// Available: otp, welcome, offer, promotion, reward, reminder,
// mining-completed, task-completed, customQueue
Priority queue with broadcast and batch support:
// Enqueue a message
const job = await khabri.queue.send("+1234567890", "Queued message");
console.log(job.id, job.status); // "queued"
// Broadcast to multiple recipients
const jobs = await khabri.queue.broadcast(["+111", "+222", "+333"], "Hello everyone!");
// Batch send with per-message options
const batch = await khabri.queue.batch([
{ to: "+111", body: "Message 1" },
{ to: "+222", body: "Message 2" },
]);
// Control
khabri.queue.pause(); // Pause processing
khabri.queue.resume(); // Resume processing
// Events
khabri.queue.on("completed", (job) => console.log(`Job ${job.id} done`));
khabri.queue.on("failed", (job, err) => console.error(`Job ${job.id} failed`, err));
// Stats
const stats = khabri.queue.stats();
// { pending: 3, processing: 1, completed: 42, failed: 0, isPaused: false }Contacts
Session-scoped contact management:
// List contacts
const contacts = await khabri.contacts.list(sessionId);
// Check if a number is registered on WhatsApp
const { exists, registeredName } = await khabri.contacts.check(sessionId, "628123456789");
// Get contact details
const contact = await khabri.contacts.get(sessionId, "contact-id");
// Get profile picture
const { profilePicture } = await khabri.contacts.profilePicture(sessionId, "contact-id");
// Block / unblock
await khabri.contacts.block(sessionId, "contact-id");
await khabri.contacts.unblock(sessionId, "contact-id");Chats
Session-scoped chat management:
// List chats
const chats = await khabri.chats.list(sessionId);
// Mark read / unread
await khabri.chats.markRead(sessionId, "[email protected]");
await khabri.chats.markUnread(sessionId, "[email protected]");
// Delete a chat
await khabri.chats.delete(sessionId, "[email protected]");
// Send typing indicator
await khabri.chats.sendTyping(sessionId, "[email protected]", "typing");
// Get message history
const messages = await khabri.chats.messages(sessionId, "[email protected]", { limit: 50 });Groups
Session-scoped group management:
// Create a group
const group = await khabri.groups.create(sessionId, "Team Chat", ["[email protected]"]);
// List groups
const groups = await khabri.groups.list(sessionId);
// Add / remove participants
await khabri.groups.addParticipants(sessionId, groupId, ["[email protected]"]);
await khabri.groups.removeParticipants(sessionId, groupId, ["[email protected]"]);
// Update subject / description
await khabri.groups.updateSubject(sessionId, groupId, "New Name");
await khabri.groups.updateDescription(sessionId, groupId, "New description");
// Leave group
await khabri.groups.leave(sessionId, groupId);Webhooks
Full CRUD management + verified receiver:
// Create a webhook
const wh = await khabri.webhooks.manager.create(sessionId, {
url: "https://example.com/webhook",
events: ["message.received", "message.sent"],
secret: "my-secret",
retryCount: 3,
});
// List webhooks
const webhooks = await khabri.webhooks.manager.list(sessionId);
// Update
await khabri.webhooks.manager.update(sessionId, wh.id, { active: false });
// Test
await khabri.webhooks.manager.test(sessionId, wh.id);
// Delete
await khabri.webhooks.manager.delete(sessionId, wh.id);
// Receive webhooks via middleware
app.post("/webhook", express.json(), khabri.webhooks.middleware());
// Listen for webhook events
khabri.on("message.received", (payload) => { /* handle */ });
khabri.on("message.ack", (payload) => { /* handle */ });
khabri.on("session.qr", (payload) => { /* handle */ });
khabri.on("session.authenticated", (payload) => { /* handle */ });
khabri.on("session.disconnected", (payload) => { /* handle */ });Status / Stories
Send and manage WhatsApp status updates (stories):
// Send text story
await khabri.status.sendText(sessionId, "Good morning!", {
backgroundColor: "#FF5733",
font: 1,
});
// Send image story
await khabri.status.sendImage(sessionId, "https://example.com/photo.jpg", {
caption: "My day!",
});
// List stories
const stories = await khabri.status.list(sessionId);
// Get stories for a contact
const contactStories = await khabri.status.get(sessionId, "[email protected]");
// Delete a story
await khabri.status.delete(sessionId, "status-id");Hooks & Middleware
Lifecycle hooks for custom logic:
// Before sending (can modify payload)
khabri.beforeSend(async (payload) => {
payload.body = payload.body.replace("{name}", userName);
return payload;
});
// After sending
khabri.afterSend(async (result) => {
console.log("Message sent:", result.id);
});
// Webhook processing pipeline
khabri.beforeWebhook(async (payload) => {
// Modify or validate webhook payload
return payload;
});
// Error handling
khabri.onError(async (error) => {
await errorReportingService.capture(error);
});Example Apps
Two example applications are included in this repository:
Express Server (apps/express-server)
A REST API server proxying to OpenWA:
cd apps/express-server
cp .env.example .env
# Edit .env with your OpenWA credentials
pnpm dev
# → http://localhost:4000
# Or from root:
pnpm dev:express| Endpoint | Description |
| --------------------------------- | ------------------------------------------------------ |
| POST /sessions | Create a new session + get QR code |
| GET /sessions | List all sessions |
| GET /sessions/:id | Get session details |
| POST /sessions/:id/start | Start session (→ qr_ready) |
| POST /sessions/:id/stop | Stop/disconnect session |
| POST /sessions/:id/force-kill | Force-kill stuck session |
| GET /sessions/:id/qr | Get QR code for pairing |
| POST /sessions/:id/pairing-code | Request pairing code |
| DELETE /sessions/:id | Delete session |
| GET /sessions/stats | Session overview statistics |
| POST /send | Send text message (requires sessionId + chatId + text) |
| POST /send/image | Send image message |
| POST /send/video | Send video message |
| POST /send/document | Send document message |
| POST /send/location | Send location message |
| POST /send/contact | Send contact card |
| POST /send/template | Send template message |
| POST /send/reply | Reply to a message |
| POST /notify/:type | Send notification (otp, welcome, etc.) |
| POST /queue/send | Enqueue a message |
| POST /queue/broadcast | Broadcast to multiple recipients |
| GET /queue/stats | Queue statistics |
| POST /webhook | Webhook receiver |
| GET /events | SSE event stream |
| GET /health | Health check |
React Dashboard (apps/react-app)
A full-featured admin dashboard with 10 tabs — all making real API calls (no mock data):
cd apps/react-app
pnpm dev
# → http://localhost:5173
# Or from root:
pnpm dev:react- Dashboard — Live API status, session counts, queue stats from
/health,/sessions,/queue/stats - Send Message — Composer for all 8 message types with sessionId + chatId fields
- Notifications — Pre-built notification forms with session ID
- Queue — Queue management with live polling stats
- Sessions — Create, start, QR code display with polling, stop, delete
- Templates — Fetch from API, create new, send with sessionId + variables
- Webhooks — Live webhook event log via SSE
- Events — Real-time SSE event stream
- Analytics — Real session stats from
/sessions/stats - Config — Server URL & API key settings (persisted to localStorage)
Run Both at Once
# From root
pnpm dev:apps
# Starts express-server (port 4000) + react-app (port 5173) in parallelAuthentication
Khabri uses the X-API-Key header for authentication (matching OpenWA API v0.7.5).
const khabri = new Khabri({
url: "https://openwa.your-instance.com",
apiKey: "owa_k1_your_api_key_here",
});Error Handling
Khabri provides a comprehensive error hierarchy:
import { KhabriError, AuthenticationError, ValidationError } from "venm-khabri";
try {
await khabri.messages.text(sessionId, "invalid", "Hello");
} catch (err) {
if (err instanceof ValidationError) {
console.error("Invalid input:", err.details);
} else if (err instanceof AuthenticationError) {
console.error("Auth failed:", err.message);
// err.recoverable === true — retry with new credentials
} else if (err instanceof RateLimitError) {
console.error("Rate limited, retry after:", err.retryAfter);
}
}| Error Class | Code | Recoverable | Description |
| --------------------- | ---------------- | ----------- | ------------------------ |
| KhabriError | KHABRI_ERR | — | Base error class |
| ConfigurationError | CONFIG_ERR | ✅ | Invalid configuration |
| AuthenticationError | AUTH_ERR | ✅ | API key issues |
| ValidationError | VALIDATION_ERR | ❌ | Input validation failure |
| RateLimitError | RATE_LIMIT | ✅ | Rate limit exceeded |
| NetworkError | NETWORK_ERR | ✅ | Network failure |
| ApiError | API_ERR | ❌ | API returned error |
| TimeoutError | TIMEOUT_ERR | ✅ | Request timed out |
Configuration
interface KhabriConfig {
url: string; // OpenWA API base URL
apiKey: string; // Your API key (X-API-Key header)
timeout?: number; // Request timeout in ms (default: 30000)
retries?: number; // Max retry attempts (default: 3)
retryDelay?: number; // Base retry delay in ms (default: 1000)
queueConcurrency?: number; // Queue concurrent messages (default: 5)
logLevel?: "debug"|"info"|"warn"|"error"|"silent"; // (default: "info")
webhookSecret?: string; // HMAC secret for webhook verification
}Development
# Clone the repo
git clone https://github.com/venm-sdk/khabri.git
cd khabri
# Install dependencies
pnpm install
# Build the SDK
pnpm build
# Run tests (144+ tests)
pnpm test
# Type check
pnpm typecheck
# Start example apps
pnpm dev:express # Express API server on :4000
pnpm dev:react # React dashboard on :5173
pnpm dev:apps # Both in parallelLicense
MIT © Venm SDK
