npm package: @saiimoon/waba
Description: WhatsApp Cloud API v25.0 SDK — zero dependencies, full coverage, TypeScript-first
Author: Saimon Marcolino Ventura
Latest version: 0.0.1
Deprecated: Moved to @saimonventura/waba

# waba

**WhatsApp Cloud API SDK for TypeScript**

[![npm](https://img.shields.io/npm/v/@saiimoon/waba)](https://www.npmjs.com/package/@saiimoon/waba) [![license](https://img.shields.io/npm/l/@saiimoon/waba)](LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/) [![tests](https://img.shields.io/badge/tests-101%20passing-brightgreen)]() [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)]()

- **Zero dependencies** -- uses native `fetch` and `FormData`
- **Full Cloud API v25.0** -- messaging, templates, media, webhooks, flows, analytics, and more
- **101 tests** -- comprehensive coverage across all API surfaces
- **TypeScript-first** -- strict types, 48 exported interfaces
- **Runtime-agnostic** -- Node.js 18+, Bun, Deno

## Quick Start

```ts
import { WhatsApp } from "@saiimoon/waba"

const wa = new WhatsApp({ phoneNumberId: "YOUR_ID", accessToken: "YOUR_TOKEN" })
await wa.sendText("5511999999999", "Hello from waba!")
```

## Install

```bash
npm install @saiimoon/waba
```

## API Overview

66 methods covering the entire WhatsApp Cloud API.

| 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` |
| **Status** | `markAsRead` `sendTypingIndicator` |
| **Templates** | `sendTemplate` `listTemplates` `createTemplate` `deleteTemplate` |
| **Media** | `uploadMedia` `getMediaUrl` `downloadMedia` `deleteMedia` |
| **Business Profile** | `getBusinessProfile` `updateBusinessProfile` |
| **Phone** | `getPhoneInfo` `registerPhone` `deregisterPhone` `requestVerificationCode` `verifyCode` |
| **Two-Step Verification** | `setTwoStepPin` `removeTwoStepPin` |
| **Block / Unblock** | `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` |
| **Broadcast** | `broadcastTemplate` |
| **Webhooks** | `verifyWebhook` `parseWebhook` `validateSignature` `parseWebhookWithSignature` |

## Usage

### Setup

```ts
import { WhatsApp } from "@saiimoon/waba"

const wa = new WhatsApp({
  phoneNumberId: "YOUR_PHONE_NUMBER_ID",
  accessToken: "YOUR_ACCESS_TOKEN",
  wabaId: "YOUR_WABA_ID",   // required for templates, analytics, flows, health
  apiVersion: "v25.0",      // optional, defaults to v25.0
})
```

### Text Messages

```ts
await wa.sendText(to, "Hello!")
await wa.sendText(to, "Check this: https://example.com", { previewUrl: true })
await wa.sendText(to, "Replying to you", { replyTo: "wamid.xxx" })
```

### Media Messages

```ts
await wa.sendImage(to, { url: "https://example.com/photo.jpg" }, "Caption")
await wa.sendAudio(to, { id: "media_id_from_upload" })
await wa.sendVideo(to, { url: "https://example.com/video.mp4" }, "Caption")
await wa.sendDocument(to, { url: "https://example.com/file.pdf" }, "invoice.pdf", "Your invoice")
await wa.sendSticker(to, { url: "https://example.com/sticker.webp" })
```

### Location and Contacts

```ts
await wa.sendLocation(to, {
  lat: -23.55,
  lng: -46.63,
  name: "Office",
  address: "Sao Paulo, SP",
})

await wa.sendContacts(to, [{
  name: { formatted_name: "Jane Doe" },
  phones: [{ phone: "+5511999999999" }],
}])
```

### Reactions

```ts
await wa.sendReaction(to, "wamid.xxx", "\u{1f44d}")
await wa.removeReaction(to, "wamid.xxx")
```

### Interactive Messages

```ts
// Buttons (up to 3)
await wa.sendButtons(to, "Choose an option:", [
  { id: "yes", title: "Yes" },
  { id: "no", title: "No" },
], { header: "Confirm", footer: "Tap a button" })

// List
await wa.sendList(to, "Our menu:", "View options", [
  {
    title: "Category A",
    rows: [
      { id: "item_1", title: "Item 1", description: "Description" },
      { id: "item_2", title: "Item 2" },
    ],
  },
])

// CTA URL
await wa.sendCTA(to, "Visit our website", {
  text: "Open",
  url: "https://example.com",
})

// Single Product
await wa.sendProduct(to, "CATALOG_ID", "PRODUCT_RETAILER_ID", "Check this out")

// Multi-Product List
await wa.sendProductList(to, "Our Products", "Browse our catalog", "CATALOG_ID", [
  {
    title: "Featured",
    product_items: [{ product_retailer_id: "SKU_001" }, { product_retailer_id: "SKU_002" }],
  },
])

// Catalog
await wa.sendCatalog(to, "Browse our full catalog", {
  thumbnailProductId: "SKU_001",
  footer: "Tap to view",
})

// Location Request
await wa.sendLocationRequest(to, "Please share your delivery address")

// Address Message
await wa.sendAddressMessage(to, "Confirm your address", {
  country: "BR",
  values: { city: "Sao Paulo", name: "Jane Doe" },
})

// Flow
await wa.sendFlow(to, "Complete your order", {
  flowId: "FLOW_ID",
  flowCta: "Start",
  mode: "published",
  flowToken: "optional_token",
  flowActionData: { screen_data: "value" },
  navigateScreen: "SCREEN_0",
})

// Voice Call
await wa.sendVoiceCall(to, "Call us for support", "+5511999999999")
```

### Status

```ts
await wa.markAsRead("wamid.xxx")
await wa.sendTypingIndicator("wamid.xxx")
```

### Templates

```ts
// Send a template
await wa.sendTemplate(to, "hello_world", "en_US")

// Send with parameters
await wa.sendTemplate(to, "order_update", "pt_BR", [
  { type: "body", parameters: [{ type: "text", text: "Order #123" }] },
])

// List templates (requires wabaId)
const templates = await wa.listTemplates({ status: "APPROVED" })

// Create a template
await wa.createTemplate({
  name: "shipping_update",
  language: "en_US",
  category: "UTILITY",
  components: [{ type: "BODY", text: "Your order {{1}} has shipped." }],
})

// Delete a template
await wa.deleteTemplate("shipping_update")
```

### Media Management

```ts
const { id } = await wa.uploadMedia(fileBytes, "image/jpeg")
const { url } = await wa.getMediaUrl(id)
const bytes = await wa.downloadMedia(url)
await wa.deleteMedia(id)
```

### Business Profile

```ts
const profile = await wa.getBusinessProfile()
await wa.updateBusinessProfile({
  about: "We deliver great products",
  description: "Open Mon-Fri 9am-6pm",
})
```

### Phone Management

```ts
const info = await wa.getPhoneInfo()
await wa.registerPhone("123456")
await wa.deregisterPhone()
await wa.requestVerificationCode("SMS")
await wa.verifyCode("123456")
```

### Two-Step Verification

```ts
await wa.setTwoStepPin("123456")
await wa.removeTwoStepPin()
```

### Block and Unblock

```ts
await wa.blockUser(["5511999999999"])
await wa.unblockUser(["5511999999999"])
```

### Commerce Settings

```ts
const settings = await wa.getCommerceSettings()
await wa.updateCommerceSettings({
  is_cart_enabled: true,
  is_catalog_visible: true,
})
```

### Health Status

```ts
// Requires wabaId
const health = await wa.getHealthStatus()
console.log(health.health_status.can_send_message) // "AVAILABLE" | "LIMITED" | "BLOCKED"
```

### Phone Numbers

```ts
// Requires wabaId
const { data: numbers } = await wa.listPhoneNumbers()
for (const num of numbers) {
  console.log(num.display_phone_number, num.verified_name, num.quality_rating)
}
```

### QR Codes

```ts
const qr = await wa.createQR("Hello! How can I help you?", "png")
console.log(qr.deep_link_url, qr.qr_image_url)

const { data: codes } = await wa.listQRCodes()
await wa.updateQR(codes[0].code, "Updated message")
await wa.deleteQR(codes[0].code)
```

### Flows

```ts
// Create and manage flows (requires wabaId)
const { id } = await wa.createFlow("Order Flow", ["OTHER"])

const { data: flows } = await wa.listFlows()
const flow = await wa.getFlow(id)

await wa.updateFlow(id, { name: "Updated Flow" })
await wa.updateFlowJSON(id, JSON.stringify({ version: "5.0", screens: [] }))
await wa.publishFlow(id)
await wa.deprecateFlow(id)

const { data: assets } = await wa.getFlowAssets(id)
await wa.deleteFlow(id)
```

### Analytics

```ts
// All analytics methods require wabaId. Timestamps are Unix epoch in seconds.
const start = Math.floor(Date.now() / 1000) - 86400 * 30
const end = Math.floor(Date.now() / 1000)

const analytics = await wa.getAnalytics(start, end, "DAY")
const conversations = await wa.getConversationAnalytics(start, end, "DAY")
const templateStats = await wa.getTemplateAnalytics(start, end, ["TEMPLATE_ID"])
```

### Broadcast

```ts
const result = await wa.broadcastTemplate(
  ["5511999999999", "5511888888888", "5511777777777"],
  "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}`)
```

## Webhooks

Webhook helpers are available as both standalone functions and static methods on `WhatsApp`.

### Verification (GET)

```ts
import { verifyWebhook } from "@saiimoon/waba"

// Express / Fastify / any framework
app.get("/webhook", (req, res) => {
  const challenge = verifyWebhook(req.query, "YOUR_VERIFY_TOKEN")
  res.send(challenge)
})
```

### Parsing Events (POST)

```ts
import { parseWebhook } from "@saiimoon/waba"

const events = parseWebhook(req.body)

for (const event of events) {
  switch (event.type) {
    case "message":
      console.log(event.message.type, event.contact.profile.name)
      break
    case "status":
      console.log(event.status.status) // "sent" | "delivered" | "read" | "failed"
      break
    case "error":
      console.log(event.errors)
      break
  }
}
```

### HMAC Signature Verification

Validate that webhooks originate from Meta using your App Secret:

```ts
import { validateSignature, parseWebhookWithSignature } from "@saiimoon/waba"

// Verify only
const isValid = validateSignature(rawBody, req.headers["x-hub-signature-256"], APP_SECRET)

// Verify + parse in one step (throws on invalid signature)
const events = parseWebhookWithSignature(rawBody, req.headers["x-hub-signature-256"], APP_SECRET)
```

## Error Handling

All API errors are thrown as `WhatsAppError` with structured fields:

```ts
import { WhatsAppError } from "@saiimoon/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.title)      // Error subcode (e.g. "131030/2494110")
    console.log(err.httpStatus) // HTTP status (e.g. 400, 429)
    console.log(err.details)    // fbtrace_id for Meta support
    console.log(err.message)    // Human-readable error message
  }
}
```

## TypeScript

All types are exported:

```ts
import type {
  // Config
  WhatsAppConfig,
  SendMessageOptions,
  InteractiveOptions,

  // Results
  SendMessageResult,
  BroadcastResult,
  MediaUploadResult,
  MediaUrlResult,

  // Messaging
  MediaSource,
  Button,
  ListSection,
  ListRow,
  CTAAction,
  LocationData,
  ContactData,
  ProductSection,
  FlowAction,
  AddressMessageOptions,

  // Templates
  TemplateComponent,
  TemplateParameter,
  TemplateCreateRequest,

  // Business
  BusinessProfile,
  PhoneInfo,
  CommerceSettings,
  HealthStatusResponse,
  PhoneNumberEntry,
  QRCode,
  FlowInfo,

  // Webhooks
  WebhookEvent,
  InboundMessage,
  StatusUpdate,
  Contact,
  Metadata,
  RawWebhookPayload,
  WebhookError,
  VerifyQuery,
} from "@saiimoon/waba"
```

## License

MIT