The complete WhatsApp Cloud API SDK for TypeScript. Zero dependencies. 81 methods. Every API surface covered.
import { WhatsApp } from "@saimonventura/waba"
const wa = new WhatsApp({
phoneNumberId: "YOUR_PHONE_NUMBER_ID",
accessToken: "YOUR_ACCESS_TOKEN",
})
await wa.sendText("5511999999999", "Hello from waba!")| waba | whatsapp-api-js | @kapso/whatsapp-cloud-api | |
|---|---|---|---|
| API methods | 79 | ~30 | ~25 |
| Dependencies | 0 | 1+ | 5+ (Zod, etc.) |
| Flows API | 9 methods | - | - |
| Analytics API | 3 methods | - | - |
| QR Codes API | 4 methods | - | - |
| Commerce API | Full | Partial | - |
| Broadcast | Built-in (batched) | - | - |
| Webhook parsing | Typed + HMAC | Typed | Basic |
| Bundle size | ~15KB | ~50KB | ~80KB |
| TypeScript | Strict, 49 interfaces | Yes | Yes |
| Runtime | Node 18+, Bun, Deno | Node | Node |
waba uses only native fetch and FormData — no polyfills, no bloat, works everywhere.
npm install @saimonventura/waba
# or
pnpm add @saimonventura/waba
# or
bun add @saimonventura/wabaimport { WhatsApp } from "@saimonventura/waba"
const wa = new WhatsApp({
phoneNumberId: "YOUR_PHONE_NUMBER_ID",
accessToken: "YOUR_ACCESS_TOKEN",
wabaId: "YOUR_WABA_ID", // needed for templates, analytics, flows
apiVersion: "v25.0", // optional, defaults to v25.0
})await wa.sendText(to, "Hello!")
await wa.sendText(to, "Check this: https://example.com", { previewUrl: true })
await wa.sendText(to, "Replying", { replyTo: "wamid.xxx" })await wa.sendImage(to, { url: "https://example.com/photo.jpg" }, "Caption")
await wa.sendVideo(to, { url: "https://example.com/video.mp4" }, "My video")
await wa.sendDocument(to, { url: "https://example.com/file.pdf" }, "invoice.pdf", "Your invoice")
await wa.sendAudio(to, { id: "media_id_from_upload" })
await wa.sendSticker(to, { url: "https://example.com/sticker.webp" })await wa.sendLocation(to, { lat: -23.55, lng: -46.63, name: "Office", address: "Sao Paulo" })
await wa.sendContacts(to, [{
name: { formatted_name: "Jane Doe" },
phones: [{ phone: "+5511999999999" }],
}])await wa.sendReaction(to, "wamid.xxx", "\u{1f44d}")
await wa.removeReaction(to, "wamid.xxx")await wa.sendButtons(to, "Choose an option:", [
{ id: "yes", title: "Yes" },
{ id: "no", title: "No" },
], { header: "Confirm", footer: "Tap a button" })await wa.sendList(to, "Our menu:", "View options", [
{
title: "Burgers",
rows: [
{ id: "classic", title: "Classic Burger", description: "$9.90" },
{ id: "cheese", title: "Cheeseburger", description: "$11.90" },
],
},
])await wa.sendCTA(to, "Visit our website", { text: "Open", url: "https://example.com" })await wa.sendProduct(to, "CATALOG_ID", "PRODUCT_ID", "Check this out")
await wa.sendProductList(to, "Our Products", "Browse our catalog", "CATALOG_ID", sections)
await wa.sendCatalog(to, "See everything we have!", { thumbnailProductId: "PRODUCT_ID" })await wa.sendLocationRequest(to, "Please share your delivery address")await wa.sendFlow(to, "Complete your order", {
flowId: "FLOW_ID",
flowCta: "Start",
mode: "published",
})Send payment requests via PIX (Brazil) or UPI (India):
await wa.sendOrderDetails(to, "Your order summary", {
referenceId: "order_123",
paymentType: "br", // "br" for PIX, "upi" for India
paymentConfiguration: "CONFIG_ID", // from Meta Business Manager
currency: "BRL",
totalAmount: { value: 2990, offset: 100 }, // R$ 29.90
order: {
status: "pending",
items: [{
retailer_id: "burger_01",
name: "Classic Burger",
amount: { value: 1990, offset: 100 },
quantity: 1,
}],
subtotal: { value: 1990, offset: 100 },
tax: { value: 500, offset: 100 },
shipping: { value: 500, offset: 100 },
},
}, { header: "Your Order", footer: "Pay with PIX" })await wa.sendOrderStatus(to, "Payment confirmed!", {
referenceId: "order_123",
order: { status: "completed", description: "PIX received" },
})// Send
await wa.sendTemplate(to, "hello_world", "en_US")
// With parameters
await wa.sendTemplate(to, "order_update", "pt_BR", [
{ type: "body", parameters: [{ type: "text", text: "Order #123" }] },
])
// CRUD (requires wabaId)
const templates = await wa.listTemplates({ status: "APPROVED" })
await wa.createTemplate({ name: "my_template", language: "pt_BR", category: "UTILITY", components: [] })
await wa.deleteTemplate("my_template")Send templates to thousands of recipients with automatic batching and rate limiting:
const result = await wa.broadcastTemplate(
["5511999999999", "5511888888888", /* ...thousands more */],
"promo_campaign",
"pt_BR",
[{ type: "body", parameters: [{ type: "text", text: "20% OFF" }] }],
{ batchSize: 50, delayMs: 100 },
)
console.log(`Sent: ${result.succeeded.length}, Failed: ${result.failed.length}`)const { id } = await wa.uploadMedia(fileBytes, "image/jpeg")
const { url } = await wa.getMediaUrl(id)
const bytes = await wa.downloadMedia(url)
await wa.deleteMedia(id)import express from "express"
import { WhatsApp } from "@saimonventura/waba"
const app = express()
// Verification (GET)
app.get("/webhook", (req, res) => {
try {
const challenge = WhatsApp.verifyWebhook(req.query, "YOUR_VERIFY_TOKEN")
res.send(challenge)
} catch {
res.sendStatus(403)
}
})
// Receive events (POST)
app.post("/webhook", express.json(), (req, res) => {
const events = WhatsApp.parseWebhook(req.body)
for (const event of events) {
if (event.type === "message") {
console.log(`${event.contact.profile.name}: ${event.message.type}`)
}
if (event.type === "status") {
console.log(`Message ${event.status.id}: ${event.status.status}`)
}
}
res.sendStatus(200)
})import { Hono } from "hono"
import { WhatsApp } from "@saimonventura/waba"
const app = new Hono()
app.get("/webhook", (c) => {
try {
const challenge = WhatsApp.verifyWebhook(c.req.query(), "YOUR_VERIFY_TOKEN")
return c.text(challenge)
} catch {
return c.text("Forbidden", 403)
}
})
app.post("/webhook", async (c) => {
const events = WhatsApp.parseWebhook(await c.req.json())
for (const event of events) {
if (event.type === "message") {
console.log(`${event.contact.profile.name}: ${event.message.type}`)
}
}
return c.text("OK")
})Always verify signatures in production:
// Verify + parse in one step
const events = WhatsApp.parseWebhookWithSignature(rawBody, req.headers["x-hub-signature-256"], APP_SECRET)
// Or verify separately
const isValid = WhatsApp.validateSignature(rawBody, signature, APP_SECRET)Full CRUD for WhatsApp Flows:
const { id } = await wa.createFlow("Order Flow", ["OTHER"])
await wa.updateFlowJSON(id, JSON.stringify(flowDefinition))
await wa.publishFlow(id)
const flows = await wa.listFlows()
const flow = await wa.getFlow(id)
const assets = await wa.getFlowAssets(id)
await wa.deprecateFlow(id)
await wa.deleteFlow(id)const qr = await wa.createQR("Hello! How can I help?", "png")
console.log(qr.qr_image_url) // direct link to QR image
console.log(qr.deep_link_url) // wa.me deep link
const { data: codes } = await wa.listQRCodes()
await wa.updateQR(codes[0].code, "Updated message")
await wa.deleteQR(codes[0].code)const now = Math.floor(Date.now() / 1000)
const thirtyDaysAgo = now - 86400 * 30
const analytics = await wa.getAnalytics(thirtyDaysAgo, now, "DAY")
const conversations = await wa.getConversationAnalytics(thirtyDaysAgo, now, "DAILY")
const templateStats = await wa.getTemplateAnalytics(thirtyDaysAgo, now, ["TEMPLATE_ID"])const profile = await wa.getBusinessProfile()
await wa.updateBusinessProfile({ about: "We deliver great products" })
const info = await wa.getPhoneInfo()
const { data: numbers } = await wa.listPhoneNumbers()
const health = await wa.getHealthStatus()All API errors throw WhatsAppError with Meta's error codes:
import { WhatsAppError } from "@saimonventura/waba"
try {
await wa.sendText(to, "Hello")
} catch (err) {
if (err instanceof WhatsAppError) {
console.log(err.code) // Meta error code (e.g. 131030)
console.log(err.httpStatus) // HTTP status (e.g. 400, 429)
console.log(err.message) // Human-readable message
console.log(err.details) // fbtrace_id for Meta support
}
}All 49 interfaces are exported:
import type {
WhatsAppConfig, SendMessageOptions, SendMessageResult,
MediaSource, Button, ListSection, CTAAction,
LocationData, ContactData, FlowAction,
TemplateComponent, TemplateCreateRequest,
MediaUploadResult, MediaUrlResult,
BusinessProfile, PhoneInfo, CommerceSettings,
HealthStatusResponse, PhoneNumberEntry, QRCode, FlowInfo,
WebhookEvent, InboundMessage, StatusUpdate, BroadcastResult,
} from "@saimonventura/waba"| Category | Methods |
|---|---|
| Messaging | sendText sendImage sendAudio sendVideo sendDocument sendSticker sendLocation sendContacts |
| Reactions | sendReaction removeReaction |
| Interactive | sendButtons sendList sendCTA sendProduct sendProductList sendCatalog sendLocationRequest sendAddressMessage sendFlow sendVoiceCall sendOrderDetails sendOrderStatus |
| Status | markAsRead sendTypingIndicator |
| Templates | sendTemplate sendCarouselTemplate sendAuthTemplate sendCouponTemplate listTemplates getTemplate createTemplate updateTemplate deleteTemplate |
| Media | uploadMedia uploadMediaResumable getMediaUrl downloadMedia deleteMedia |
| Business Profile | getBusinessProfile updateBusinessProfile |
| Phone | getPhoneInfo registerPhone deregisterPhone requestVerificationCode verifyCode |
| Two-Step | setTwoStepPin removeTwoStepPin |
| Block | blockUser unblockUser |
| Commerce | getCommerceSettings updateCommerceSettings |
| Health | getHealthStatus |
| Phone Numbers | listPhoneNumbers |
| QR Codes | createQR listQRCodes updateQR deleteQR |
| Flows | createFlow listFlows getFlow updateFlow publishFlow deprecateFlow deleteFlow getFlowAssets updateFlowJSON |
| Analytics | getAnalytics getConversationAnalytics getTemplateAnalytics getTemplatePerformance getPricingAnalytics getCallAnalytics |
| Calling | initiateCall acceptCall rejectCall terminateCall |
| Broadcast | broadcastTemplate |
| Webhooks | verifyWebhook parseWebhook validateSignature parseWebhookWithSignature |
Contributions are welcome! See CONTRIBUTING.md for guidelines.
MIT