# WAAPI — Send Message API

Send WhatsApp messages programmatically using your vendor API key. Supports text, media, template (with variables, OTP, LTO), interactive (button, CTA URL, list, **carousel**), and carousel template message types.

---

## Base URLs

Two base URLs are available — use either:

| Base URL | Description |
|----------|-------------|
| `https://your-api.com/api` | **Backend API** — direct connection to the Node.js backend |
| `https://your-domain.com/api` | **Frontend Proxy** — Next.js server-side proxy (same body format, same token) |

Both URLs accept identical request bodies and return identical responses.
The **Frontend Proxy** is useful when you want all traffic to go through a single domain.

---

## Authentication

Every request must include a valid Bearer token in the `Authorization` header.

```
Authorization: Bearer <your_api_token>
```

**How to get the token:**
1. Go to **WhatsApp Settings → API & Webhook → API Keys**
2. Click **Create Key**
3. Copy the token shown — it is only displayed **once** at creation time

**How to use in HTTP headers:**

```http
POST /api/external/send-message/{encodedPhoneId}
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
```

**cURL example (Frontend Proxy):**

```bash
curl -X POST https://your-domain.com/api/external/send-message/{encodedPhoneId} \
  -H "Authorization: Bearer <your_api_token>" \
  -H "Content-Type: application/json" \
  -d '{"to":"+919876543210","type":"text","text":"Hello!"}'
```

**cURL example (Backend Direct):**

```bash
curl -X POST https://your-api.com/api/external/send-message/{encodedPhoneId} \
  -H "Authorization: Bearer <your_api_token>" \
  -H "Content-Type: application/json" \
  -d '{"to":"+919876543210","type":"text","text":"Hello!"}'
```

---

## Endpoints

| Method | Base | Path | Description |
|--------|------|------|-------------|
| `POST` | Backend | `/api/external/send-message/:encodedPhoneId` | Send via backend directly |
| `POST` | Frontend | `/api/external/send-message/:encodedPhoneId` | Send via Next.js server-side proxy |

> **`encodedPhoneId`** is the Base64-encoded phone number ID shown on the API Keys page.

---

## Request Body — Top-Level Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `to` | `string` | ✅ | Recipient phone number in E.164 format (e.g. `+919876543210`) |
| `type` | `string` | ✅ | Message type — see table below |
| `contact` | `object` | ❌ | Auto-create/update the contact in your WAAPI Contacts list |

### Supported `type` Values

| `type` | Permission needed | Description |
|--------|-------------------|-------------|
| `text` | `send_text` | Plain text |
| `image` | `send_media` | Image with optional caption |
| `video` | `send_media` | Video with optional caption |
| `audio` | `send_media` | Audio / voice note |
| `document` | `send_media` | PDF, DOCX, XLSX, etc. |
| `template` | `send_template` | Approved WA Business template (variables, OTP, LTO, buttons) |
| `interactive` | `send_interactive` | Interactive message — `button`, `cta_url`, `list`, or **`carousel`** |
| `carousel` | `send_template` | Carousel template message (approved template required) |

### `contact` object (optional — works with every type)

```json
"contact": {
  "name": "John Doe",
  "email": "john@example.com"
}
```

When supplied, WAAPI automatically creates or updates the contact in your Contacts list.

---

## Message Types

---

### 1. Text Message

**Permission:** `send_text`

```json
{
  "to": "+919876543210",
  "type": "text",
  "text": "Hello! How can we help you today?"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `text` | `string` | ✅ | Message body (plain text) |

---

### 2. Image Message

**Permission:** `send_media`

```json
{
  "to": "+919876543210",
  "type": "image",
  "media": {
    "url": "https://example.com/banner.jpg",
    "caption": "Check out our new collection!"
  }
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `media.url` | `string` | ✅ | Publicly accessible image URL |
| `media.caption` | `string` | ❌ | Caption text |

---

### 3. Video Message

**Permission:** `send_media`

```json
{
  "to": "+919876543210",
  "type": "video",
  "media": {
    "url": "https://example.com/promo.mp4",
    "caption": "Watch our product demo"
  }
}
```

---

### 4. Audio Message

**Permission:** `send_media`

```json
{
  "to": "+919876543210",
  "type": "audio",
  "media": {
    "url": "https://example.com/voice-note.mp3"
  }
}
```

---

### 5. Document Message

**Permission:** `send_media`

```json
{
  "to": "+919876543210",
  "type": "document",
  "media": {
    "url": "https://example.com/invoice.pdf",
    "filename": "Invoice_2025.pdf",
    "caption": "Your invoice for March 2025"
  }
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `media.url` | `string` | ✅ | Publicly accessible document URL |
| `media.filename` | `string` | ❌ | Filename shown to the recipient |
| `media.caption` | `string` | ❌ | Caption text |

---

### 6. Template Message

> **Heads up — language is auto-resolved (when unambiguous).** `template.language` is optional.
> If you omit it, WAAPI looks up the approved template on your account and uses the language Meta has on record. This eliminates the common `en` vs `en_US` mismatch (error `#132001`).
>
> **Multi-language templates:** if the same template name is approved in more than one language (e.g. `en` and `bn`), you **must** pass `language` explicitly. The API rejects ambiguous requests with the list of available languages instead of silently picking the wrong one. Use `GET /api/external/templates/{encodedPhoneId}/{name}` to see every approved variant.

> **Named parameters (Meta API ≥ v17).** If your template was approved with `parameter_format: "named"`, send `body.variables` as a key-value object (e.g. `{ "customer_name": "John", "order_id": "ORD-12345" }`). WAAPI auto-detects the format from the approved template — no extra flag needed.


Send a pre-approved WhatsApp Business template. Supports header media (image, video, document, location), body variables, quick-reply buttons, URL buttons, OTP copy-code, and Limited Time Offers (LTO).

**Permission:** `send_template`

#### 6a. Body Variables

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "order_confirmation",
    "language": "en_US",
    "body": {
      "variables": ["John", "ORD-12345", "₹1,299"]
    }
  }
}
```

#### 6b. Image Header + Variables

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "product_promo",
    "language": "en_US",
    "header": {
      "type": "image",
      "url": "https://example.com/product.jpg"
    },
    "body": {
      "variables": ["Summer Sale", "40%"]
    }
  }
}
```

#### 6c. Video Header

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "promo_video",
    "language": "en_US",
    "header": {
      "type": "video",
      "url": "https://example.com/promo.mp4"
    },
    "body": {
      "variables": ["Exclusive Offer"]
    }
  }
}
```

#### 6d. Document Header

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "invoice_ready",
    "language": "en_US",
    "header": {
      "type": "document",
      "url": "https://example.com/invoices/INV-12345.pdf",
      "filename": "INV-12345.pdf"
    },
    "body": {
      "variables": ["John", "March 2025", "₹5,000"]
    }
  }
}
```

> `header.filename` is **optional**. When omitted, WhatsApp uses the original filename inferred from the URL. Setting it lets you control the name the recipient sees on their device — useful for invoices, tickets, statements, etc.

#### 6e. Location Header

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "store_location",
    "language": "en_US",
    "header": {
      "type": "location",
      "latitude": 28.6139,
      "longitude": 77.2090,
      "name": "Our Store",
      "address": "Connaught Place, New Delhi"
    }
  }
}
```

#### 6f. Quick-Reply Buttons

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "feedback_request",
    "language": "en_US",
    "body": {
      "variables": ["your recent order"]
    },
    "buttons": [
      { "index": 0, "sub_type": "quick_reply", "payload": "RATE_GOOD" },
      { "index": 1, "sub_type": "quick_reply", "payload": "RATE_BAD" }
    ]
  }
}
```

#### 6g. URL Button

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "track_order",
    "language": "en_US",
    "body": {
      "variables": ["ORD-12345"]
    },
    "buttons": [
      { "index": 0, "sub_type": "url", "url": "ORD-12345" }
    ]
  }
}
```

> For URL buttons, `url` is the **dynamic suffix** appended to the template's base URL.

#### 6h. OTP Copy Code

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "otp_message",
    "language": "en_US",
    "body": {
      "variables": ["784521"]
    },
    "copy_code": "784521"
  }
}
```

#### 6i. Limited Time Offer (LTO) — Basic

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "flash_sale",
    "language": "en_US",
    "body": {
      "variables": ["FLASH30", "30%"]
    },
    "lto": {
      "expiration_time_ms": 1740000000000
    },
    "copy_code": "FLASH30"
  }
}
```

#### 6j. Limited Time Offer (LTO) — Image Header

LTO templates support an `image` or `video` header (document and location are **not** supported for LTO).

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "flash_sale_banner",
    "language": "en_US",
    "header": {
      "type": "image",
      "url": "https://example.com/flash-sale-banner.jpg"
    },
    "body": {
      "variables": ["FLASH30", "30%"]
    },
    "lto": {
      "expiration_time_ms": 1740000000000
    },
    "copy_code": "FLASH30"
  }
}
```

#### 6k. Limited Time Offer (LTO) — Image Header + Dynamic URL Button

Some LTO templates have both a copy-code button **and** a URL button with a dynamic suffix (e.g. a deep link to the sale page). Specify both in the `buttons` array using explicit `index` values that match the button order in your approved template.

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "flash_sale_full",
    "language": "en_US",
    "header": {
      "type": "image",
      "url": "https://example.com/flash-sale-banner.jpg"
    },
    "body": {
      "variables": ["FLASH30", "30%"]
    },
    "lto": {
      "expiration_time_ms": 1740000000000
    },
    "buttons": [
      { "index": 0, "sub_type": "copy_code", "payload": "FLASH30" },
      { "index": 1, "sub_type": "url",       "url": "?coupon=FLASH30" }
    ]
  }
}
```

> When both a copy-code and URL button are present, use the `buttons` array with **explicit `index` values** matching the button order in your approved Meta template. The `copy_code` shorthand can also be combined with URL buttons — WAAPI places the copy-code after any `buttons[]` entries automatically.

**Alternative using `copy_code` shorthand + `buttons[]` for the URL:**

```json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "flash_sale_full",
    "language": "en_US",
    "header": {
      "type": "video",
      "url": "https://example.com/flash-sale.mp4"
    },
    "body": {
      "variables": ["FLASH30", "30%"]
    },
    "lto": {
      "expiration_time_ms": 1740000000000
    },
    "buttons": [
      { "index": 0, "sub_type": "url", "url": "?coupon=FLASH30" }
    ],
    "copy_code": "FLASH30"
  }
}
```

> Here `copy_code` shorthand is placed at `index: 1` automatically (after the URL button at index 0).

#### Template Fields Reference

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `template.name` | `string` | ✅ | Approved template name |
| `template.language` | `string` | ❌ | Language code. **Auto-resolved** from the approved template when omitted. |
| `template.header.type` | `string` | — | `image`, `video`, `document`, or `location`; LTO supports only `image` or `video` |
| `template.header.url` | `string` | — | Media URL (image / video / document) |
| `template.header.filename` | `string` | ❌ | Displayed file name on the recipient's device (**document headers only**) |
| `template.header.latitude` | `number` | — | Latitude (location header) |
| `template.header.longitude` | `number` | — | Longitude (location header) |
| `template.header.name` | `string` | — | Location name |
| `template.header.address` | `string` | — | Location address |
| `template.body.variables` | `string[]` | ❌ | Positional variable values `{{1}}`, `{{2}}`, … |
| `template.buttons[].index` | `number` | ❌ | 0-based button index matching the approved template |
| `template.buttons[].sub_type` | `string` | ❌ | `quick_reply` (default), `url`, or `copy_code` |
| `template.buttons[].payload` | `string` | ❌ | Quick-reply payload; for `copy_code` this is the coupon code value |
| `template.buttons[].url` | `string` | ❌ | Dynamic URL suffix appended to the template's base URL |
| `template.copy_code` | `string` | ❌ | Shorthand: coupon/OTP code — auto-indexed after any `buttons[]` entries |
| `template.lto.expiration_time_ms` | `number` | ❌ | LTO offer expiry as Unix millisecond timestamp |

---

### 7. Interactive Button Message

Up to 3 quick-reply buttons.

**Permission:** `send_interactive`

```json
{
  "to": "+919876543210",
  "type": "interactive",
  "interactive": {
    "type": "button",
    "header": {
      "type": "text",
      "text": "Choose an option"
    },
    "body": "How would you like to proceed?",
    "footer": "Reply with a button below",
    "buttons": [
      { "id": "btn_yes",  "title": "Yes, confirm" },
      { "id": "btn_no",   "title": "No, cancel" },
      { "id": "btn_info", "title": "More info" }
    ]
  }
}
```

---

### 8. Interactive CTA URL Message

A message with a single call-to-action button that opens a URL.

**Permission:** `send_interactive`

```json
{
  "to": "+919876543210",
  "type": "interactive",
  "interactive": {
    "type": "cta_url",
    "header": {
      "type": "image",
      "url": "https://example.com/banner.jpg"
    },
    "body": "Click below to view your order details.",
    "footer": "WAAPI Store",
    "url": "https://example.com/orders/12345",
    "display_text": "View Order"
  }
}
```

---

### 9. Interactive List Message

A scrollable list picker with sections and rows.

**Permission:** `send_interactive`

```json
{
  "to": "+919876543210",
  "type": "interactive",
  "interactive": {
    "type": "list",
    "body": "Please select a department",
    "footer": "We're here to help",
    "display_text": "Choose Department",
    "sections": [
      {
        "title": "Support",
        "rows": [
          { "id": "tech_support", "title": "Technical Support", "description": "Help with technical issues" },
          { "id": "billing",      "title": "Billing",           "description": "Invoices and payments" }
        ]
      },
      {
        "title": "Sales",
        "rows": [
          { "id": "new_order",   "title": "New Order",   "description": "Place a new order" },
          { "id": "track_order", "title": "Track Order", "description": "Check delivery status" }
        ]
      }
    ]
  }
}
```

---

### 10. Interactive Carousel Message

A horizontally scrollable carousel of cards sent as a **live interactive message** — no template approval needed. Each card has its own image/video header, body text, and action buttons (quick-reply and/or CTA URL, which can coexist on the same card).

**Permission:** `send_interactive`

> **Difference from Carousel Template (§11):** This type does **not** require a pre-approved Meta template. Content is sent dynamically at runtime. Template Carousel requires a Meta-approved template and supports body variable substitution.

#### Quick-Reply Buttons per Card

```json
{
  "to": "+919876543210",
  "type": "interactive",
  "interactive": {
    "type": "carousel",
    "body": "Browse our latest products",
    "cards": [
      {
        "card_index": 0,
        "header": {
          "type": "image",
          "url": "https://example.com/product1.jpg"
        },
        "body": "Wireless Earbuds — Premium sound, all day comfort.",
        "buttons": [
          { "id": "buy_earbuds",  "title": "Buy Now" },
          { "id": "info_earbuds", "title": "Learn More" }
        ]
      },
      {
        "card_index": 1,
        "header": {
          "type": "image",
          "url": "https://example.com/product2.jpg"
        },
        "body": "Smart Watch — Stay connected on the go.",
        "buttons": [
          { "id": "buy_watch",  "title": "Buy Now" },
          { "id": "info_watch", "title": "Learn More" }
        ]
      }
    ]
  }
}
```

#### CTA URL Buttons per Card

```json
{
  "to": "+919876543210",
  "type": "interactive",
  "interactive": {
    "type": "carousel",
    "body": "Explore our top deals",
    "cards": [
      {
        "card_index": 0,
        "header": {
          "type": "image",
          "url": "https://example.com/deal1.jpg"
        },
        "body": "Flat 50% off on headphones. Limited stock!",
        "cta_url": {
          "display_text": "Shop Now",
          "url": "https://example.com/deals/headphones"
        }
      },
      {
        "card_index": 1,
        "header": {
          "type": "video",
          "url": "https://example.com/deal2.mp4"
        },
        "body": "New arrivals — Smartwatch collection.",
        "cta_url": {
          "display_text": "View Collection",
          "url": "https://example.com/deals/smartwatches"
        }
      }
    ]
  }
}
```

#### Mixed — Quick-Reply + CTA URL on Same Card

Both `buttons` (quick-reply) and `cta_url` can appear on the same card simultaneously.

```json
{
  "to": "+919876543210",
  "type": "interactive",
  "interactive": {
    "type": "carousel",
    "body": "Choose your favourite product",
    "cards": [
      {
        "card_index": 0,
        "header": {
          "type": "image",
          "url": "https://example.com/product1.jpg"
        },
        "body": "Wireless Earbuds — ₹1,999",
        "buttons": [
          { "id": "add_cart_earbuds", "title": "Add to Cart" }
        ],
        "cta_url": {
          "display_text": "View Details",
          "url": "https://example.com/products/earbuds"
        }
      },
      {
        "card_index": 1,
        "header": {
          "type": "image",
          "url": "https://example.com/product2.jpg"
        },
        "body": "Smart Watch — ₹4,999",
        "buttons": [
          { "id": "add_cart_watch", "title": "Add to Cart" }
        ],
        "cta_url": {
          "display_text": "View Details",
          "url": "https://example.com/products/watch"
        }
      }
    ]
  }
}
```

#### Interactive Carousel Fields Reference

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `interactive.type` | `string` | ✅ | Must be `"carousel"` |
| `interactive.body` | `string` | ✅ | Carousel-level body text shown above the cards |
| `interactive.cards` | `array` | ✅ | Array of card objects (max 10 cards) |
| `interactive.cards[].card_index` | `number` | ❌ | 0-based card index (defaults to array position) |
| `interactive.cards[].header` | `object` | ✅ | Card header (image or video) |
| `interactive.cards[].header.type` | `string` | ✅ | `"image"` or `"video"` |
| `interactive.cards[].header.url` | `string` | ✅ | Publicly accessible media URL |
| `interactive.cards[].body` | `string` | ❌ | Card body text |
| `interactive.cards[].buttons` | `array` | ❌ | Quick-reply buttons (max 2 per card) |
| `interactive.cards[].buttons[].id` | `string` | ✅ | Unique button ID (returned in webhook on tap) |
| `interactive.cards[].buttons[].title` | `string` | ✅ | Button label (max 20 chars) |
| `interactive.cards[].cta_url` | `object` | ❌ | CTA URL button (can coexist with `buttons`) |
| `interactive.cards[].cta_url.display_text` | `string` | ✅ | Button label (max 20 chars) |
| `interactive.cards[].cta_url.url` | `string` | ✅ | URL opened when tapped |

> All cards **must have the same button structure** (same number and types of buttons per card).

---

### 11. Carousel Template Message

A horizontally scrollable carousel powered by a **pre-approved Meta carousel template**. Supports per-card body variable substitution and per-card buttons.

**Permission:** `send_template`

> **Difference from Interactive Carousel (§10):** This type requires a Meta-approved carousel template and supports `{{1}}`, `{{2}}` body variable substitution per card. Interactive Carousel sends content dynamically without template approval.

```json
{
  "to": "+919876543210",
  "type": "carousel",
  "carousel": {
    "template_name": "product_carousel",
    "language": "en_US",
    "cards": [
      {
        "card_index": 0,
        "header": {
          "type": "image",
          "url": "https://example.com/product1.jpg"
        },
        "body": {
          "variables": ["Wireless Earbuds", "₹1,999"]
        },
        "buttons": [
          { "index": 0, "sub_type": "quick_reply", "payload": "BUY_EARBUDS" },
          { "index": 1, "sub_type": "url",          "url": "earbuds" }
        ]
      },
      {
        "card_index": 1,
        "header": {
          "type": "image",
          "url": "https://example.com/product2.jpg"
        },
        "body": {
          "variables": ["Smart Watch", "₹4,999"]
        },
        "buttons": [
          { "index": 0, "sub_type": "quick_reply", "payload": "BUY_WATCH" },
          { "index": 1, "sub_type": "url",          "url": "watch" }
        ]
      }
    ]
  }
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `carousel.template_name` | `string` | ✅ | Approved carousel template name |
| `carousel.language` | `string` | ❌ | Language code (default: `en_US`) |
| `carousel.cards` | `array` | ✅ | Card objects (max 10) |
| `carousel.cards[].card_index` | `number` | ❌ | 0-based card index |
| `carousel.cards[].header.type` | `string` | — | `image` or `video` |
| `carousel.cards[].header.url` | `string` | — | Media URL |
| `carousel.cards[].body.variables` | `string[]` | ❌ | Positional variable values |
| `carousel.cards[].buttons[].index` | `number` | ❌ | Button index |
| `carousel.cards[].buttons[].sub_type` | `string` | ❌ | `quick_reply` (default) or `url` |
| `carousel.cards[].buttons[].payload` | `string` | ❌ | Quick-reply payload |
| `carousel.cards[].buttons[].url` | `string` | ❌ | URL suffix for URL buttons |

---

### Interactive Fields Reference (button / cta_url / list)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `interactive.type` | `string` | ✅ | `button`, `cta_url`, `list`, or `carousel` |
| `interactive.header.type` | `string` | — | `text`, `image`, `video`, or `document` (not used for carousel) |
| `interactive.header.text` | `string` | — | Header text (when type=text) |
| `interactive.header.url` | `string` | — | Media URL (when type=image/video/document) |
| `interactive.header.filename` | `string` | ❌ | Displayed file name on the recipient's device (**document headers only**) |
| `interactive.body` | `string` | ❌ | Body text |
| `interactive.footer` | `string` | ❌ | Footer text (max 60 chars; not used for carousel) |
| `interactive.buttons` | `array` | — | Buttons for type=button (max 3) |
| `interactive.buttons[].id` | `string` | — | Unique button ID |
| `interactive.buttons[].title` | `string` | — | Button label (max 20 chars) |
| `interactive.sections` | `array` | — | Sections for type=list |
| `interactive.url` | `string` | — | URL for type=cta_url |
| `interactive.display_text` | `string` | — | Button label for cta_url / list selector |

---

## Response

```json
{
  "success": true,
  "data": {
    "message_id": "6789abcdef1234567890abcd",
    "wa_message_id": "wamid.HBgLMTIzNDU2Nzg5MBUCABIYFzNFQjA1NkU2MzYzMEExQzlENjM3QzgA",
    "conversation_id": "6789abcdef1234567890aaaa",
    "status": "sent"
  },
  "message": "Message sent successfully."
}
```

| Field | Description |
|-------|-------------|
| `message_id` | Internal WAAPI message document ID |
| `wa_message_id` | WhatsApp message ID returned by Meta |
| `conversation_id` | WAAPI conversation thread ID (auto-created if first message to this contact) |
| `status` | `sent` or `failed` |

---

## Error Responses

| HTTP | Description |
|------|-------------|
| `400` | Missing required field or invalid format |
| `401` | Missing or invalid Authorization token |
| `403` | Token not permitted for this phone number or message type |
| `502` | Meta API rejected the message — details in `error.message` |

```json
{
  "success": false,
  "error": "interactive.cards is required for type=carousel."
}
```

---

## Discover Approved Templates

Instead of guessing template names and languages, list the approved templates for this account directly via the same Bearer token.

```http
GET /api/external/templates/{encodedPhoneId}
GET /api/external/templates/{encodedPhoneId}/{name}
Authorization: Bearer <your_api_token>
```

**Required permission:** `read_templates` — enable it from **WhatsApp Settings → API & Webhook → API Keys**.

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by Meta status (default: `APPROVED`). Pass `"all"` to disable. |
| `language` | string | Filter to a single language code |
| `search` | string | Case-insensitive substring on `template_name` |
| `page` | number | Page number (default: 1) |
| `limit` | number | Page size (default: 50, max: 200) |

### Response

```json
{
  "success": true,
  "data": {
    "templates": [
      {
        "template_name": "invoice_ready",
        "language": "en",
        "category": "utility",
        "meta_status": "APPROVED",
        "meta_template_id": "1219262932664098",
        "quality_score": "GREEN",
        "parameter_format": "positional",
        "header": {
          "format": "DOCUMENT",
          "text": null,
          "variable_count": 0,
          "example": null
        },
        "body": {
          "text": "Hello {{1}}, your invoice for {{2}} is ready. Total: {{3}}.",
          "variable_count": 3,
          "example": null
        },
        "footer": { "text": "Powered by WAAPI" },
        "buttons": [],
        "raw_components": [ /* full Meta components array */ ]
      }
    ],
    "pagination": { "page": 1, "limit": 50, "total": 1, "totalPages": 1 }
  },
  "message": "Templates retrieved successfully."
}
```

---

## Permissions Reference

| Message Type / Action | Required Permission |
|----------------------|---------------------|
| `text` | `send_text` |
| `image`, `video`, `audio`, `document` | `send_media` |
| `template`, `carousel` (template) | `send_template` |
| `interactive` — button / cta_url / list / **carousel** | `send_interactive` |
| List / get approved templates (`GET /api/external/templates/*`) | `read_templates` |

---

## Carousel Comparison

| Feature | Interactive Carousel (`type: "interactive"`) | Template Carousel (`type: "carousel"`) |
|---------|----------------------------------------------|----------------------------------------|
| Meta template required | ❌ No | ✅ Yes (must be approved) |
| Dynamic content | ✅ Yes — fully dynamic | Partial — variables only |
| Body variable substitution | ❌ No | ✅ Yes `{{1}}`, `{{2}}`, … |
| Header types | `image`, `video` | `image`, `video` |
| Button types | Quick-reply, CTA URL (mixed) | Quick-reply, URL |
| Permission | `send_interactive` | `send_template` |

---

## Full Example — Template with Contact Auto-Create

```json
{
  "to": "+919876543210",
  "type": "template",
  "contact": {
    "name": "Ravi Kumar",
    "email": "ravi@example.com"
  },
  "template": {
    "name": "welcome_message",
    "language": "en_US",
    "body": {
      "variables": ["Ravi"]
    }
  }
}
```

---

## Webhooks

After every successful send, WAAPI fires a `message.sent` event to all active vendor webhooks subscribed to that account. Configure webhooks from the **Setup → Webhooks** page in the WAAPI dashboard.

**`message.sent` payload:**

```json
{
  "event": "message.sent",
  "message_id": "6789abcdef1234567890abcd",
  "wa_message_id": "wamid.HBgL...",
  "conversation_id": "6789abcdef1234567890aaaa",
  "to": "+919876543210",
  "type": "text",
  "status": "sent",
  "timestamp": "2026-03-05T10:30:00.000Z"
}
```

The request includes a `X-Webhook-Signature` header (HMAC-SHA256 of the body using your webhook secret) so you can verify authenticity.

---

## Notes

- Phone numbers must be in **E.164 format** (e.g. `+919876543210`).
- All media URLs must be **publicly accessible** HTTPS links.
- `template` and `carousel` (template) messages require Meta-approved templates.
- Interactive Carousel does **not** require a Meta template — content is sent dynamically.
- All cards in a carousel must have the **same button structure** (same number and types of buttons).
- The `send_interactive` permission must be explicitly enabled when creating the API key.
- Every successful send automatically creates or updates the WAAPI conversation thread for the recipient. The `conversation_id` is returned in the response.
