Send WhatsApp Messages
A single endpoint that sends text, media, interactive, template and carousel messages — all secured by a vendor Bearer token.
Reference
The Send Message API delivers WhatsApp messages from your vendor account using your API key. It accepts a single JSON body and automatically routes the payload through the WhatsApp Cloud API, returning both the internal message id and the Meta wa_message_id.
Resources
Download the collection
Grab the ready-to-use Postman collection and the full Markdown reference. The Postman file ships with every example pre-filled — just swap the variables.
Endpoints
Base URLs
Both URLs accept identical request bodies and return identical responses.
| 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, same token) |
Security
Authentication
Every request must include a valid Bearer token in the Authorization header. Generate a token from WhatsApp Settings → API & Webhook → API Keys — it is only shown once at creation time.
POST /api/external/send-message/{encodedPhoneId}
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
cURL — Frontend Proxy
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 — Backend Direct
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!"}'
Routes
Endpoints
/api/external/send-message/:encodedPhoneIdencodedPhoneId is the Base64-encoded WhatsApp phone-number id shown on the API Keys page.Schema
Request body — top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient phone number in E.164 format (e.g. +919876543210) |
type | string | Yes | Message type — see Message types section |
contact | object | No | Auto-create/update the contact in your WAAPI Contacts list |
Supported type values
| type | Permission | 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 template (variables, OTP, LTO, buttons) |
interactive | send_interactive | Interactive — button, cta_url, list, carousel |
carousel | send_template | Carousel template message |
Auto-create contact
When supplied, WAAPI automatically creates or updates the contact in your Contacts list.
{
"contact": {
"name": "John Doe",
"email": "john@example.com"
}
}Payloads
Message types
All examples below are full request bodies — wrap them in the auth header and POST to the endpoint above.
1. Text
{
"to": "+919876543210",
"type": "text",
"text": "Hello! How can we help you today?"
}2. Image / Video / Audio / Document
{
"to": "+919876543210",
"type": "image",
"media": {
"url": "https://example.com/banner.jpg",
"caption": "Check out our new collection!"
}
}{
"to": "+919876543210",
"type": "video",
"media": {
"url": "https://example.com/promo.mp4",
"caption": "Watch our product demo"
}
}{
"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 | Yes | Publicly accessible HTTPS URL |
media.caption | string | No | Caption text |
media.filename | string | No | Filename shown to the recipient (document only) |
3. Template
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).
Language is auto-resolved (when unambiguous)
template.language is optional. If you omit it, WAAPI looks up the approved template on this account and uses the language Meta has on record — so en vs en_US mismatches just work.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/:phoneId/:name to see every approved variant.{
"to": "+919876543210",
"type": "template",
"template": {
"name": "order_confirmation",
"body": { "variables": ["John", "ORD-12345", "₹1,299"] }
}
}For templates approved with parameter_format: "named" (Meta API ≥ v17), pass a key-value object instead of a positional array. WAAPI detects the format from the approved template automatically.
{
"to": "+919876543210",
"type": "template",
"template": {
"name": "order_confirmation",
"body": {
"variables": {
"customer_name": "John",
"order_id": "ORD-12345",
"total": "₹1,299"
}
}
}
}{
"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%"] }
}
}{
"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"] }
}
}template.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.{
"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" }
]
}
}{
"to": "+919876543210",
"type": "template",
"template": {
"name": "otp_message",
"language": "en_US",
"body": { "variables": ["784521"] },
"copy_code": "784521"
}
}{
"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"
}
}Template fields reference
| Field | Type | Required | Description |
|---|---|---|---|
template.name | string | Yes | Approved template name |
template.language | string | No | Language code. Auto-resolved from the approved template when omitted. |
template.header.type | string | — | text, image, video, document or location (LTO: image/video only) |
template.header.url | string | — | Media URL (image/video/document) |
template.header.filename | string | No | Displayed file name on the recipient device (document headers only) |
template.header.variables | string[] | No | Positional values for a text-header with {{1}}, {{2}}, … |
template.body.variables | string[] | object | No | Array for positional templates, object for named-parameter templates |
template.buttons[].index | number | No | 0-based button index matching the approved template |
template.buttons[].sub_type | string | No | quick_reply (default), url, or copy_code |
template.buttons[].payload | string | No | Quick-reply payload; for copy_code this is the coupon value |
template.buttons[].url | string | No | Dynamic URL suffix appended to the template base URL |
template.copy_code | string | No | Coupon/OTP shorthand — auto-indexed after buttons[] |
template.lto.expiration_time_ms | number | No | LTO offer expiry (Unix milliseconds) |
4. Interactive — button / cta_url / list / carousel
Live interactive messages — no template approval needed. Up to 3 quick-reply buttons or a scrollable list / carousel.
{
"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" }
]
}
}{
"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"
}
}{
"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" }
]
}
]
}
}{
"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" }
]
}
]
}
}5. 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.
{
"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" }
]
}
]
}
}Discovery
Discover approved templates
Instead of guessing template names and languages, list the approved templates for this account directly via the same Bearer token. Useful for automation, dashboards, and pre-flight validation.
/api/external/templates/:encodedPhoneId/api/external/templates/:encodedPhoneId/:nameread_templates permission on the API key. Enable it from WhatsApp Settings → API & Webhook → API Keys.List 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) |
curl "https://your-api.com/api/external/templates/{phoneId}?search=invoice" \
-H "Authorization: Bearer <your_api_token>"
Response shape
{
"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."
}Use this to drive the API tester
Output
Response
{
"success": true,
"data": {
"message_id": "6789abcdef1234567890abcd",
"wa_message_id": "wamid.HBgL...",
"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 on first send) |
status | sent or failed |
Edge cases
Errors
| 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 |
{
"success": false,
"error": "interactive.cards is required for type=carousel."
}Access control
Permissions
| Message type | Required permission |
|---|---|
text | send_text |
image, video, audio, document | send_media |
template, carousel (template) | send_template |
interactive (button/cta_url/list/carousel) | send_interactive |
Read approved templates (GET /api/external/templates/*) | read_templates |
Notifications
Outbound webhook — message.sent
After every successful send, WAAPI fires a message.sent event to all active vendor webhooks subscribed to the account. The request includes an X-Webhook-Signature header (HMAC-SHA256 of the body) so you can verify authenticity.
{
"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"
}Configure webhooks
Setup → Webhooks in the dashboard, or read the Webhooks reference.