SEND MESSAGE API

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 URLDescription
https://your-api.com/apiBackend API — direct connection to the Node.js backend
https://your-domain.com/apiFrontend 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.

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

cURL — Frontend Proxy

terminal
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

terminal
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

POST/api/external/send-message/:encodedPhoneId
encodedPhoneId is the Base64-encoded WhatsApp phone-number id shown on the API Keys page.

Schema

Request body — top-level fields

FieldTypeRequiredDescription
tostringYesRecipient phone number in E.164 format (e.g. +919876543210)
typestringYesMessage type — see Message types section
contactobjectNoAuto-create/update the contact in your WAAPI Contacts list

Supported type values

typePermissionDescription
textsend_textPlain text
imagesend_mediaImage with optional caption
videosend_mediaVideo with optional caption
audiosend_mediaAudio / voice note
documentsend_mediaPDF, DOCX, XLSX, etc.
templatesend_templateApproved template (variables, OTP, LTO, buttons)
interactivesend_interactiveInteractive — button, cta_url, list, carousel
carouselsend_templateCarousel template message

Auto-create contact

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

contact (optional)
{
  "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

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

2. Image / Video / Audio / Document

image.json
{
  "to": "+919876543210",
  "type": "image",
  "media": {
    "url": "https://example.com/banner.jpg",
    "caption": "Check out our new collection!"
  }
}
video.json
{
  "to": "+919876543210",
  "type": "video",
  "media": {
    "url": "https://example.com/promo.mp4",
    "caption": "Watch our product demo"
  }
}
document.json
{
  "to": "+919876543210",
  "type": "document",
  "media": {
    "url": "https://example.com/invoice.pdf",
    "filename": "Invoice_2025.pdf",
    "caption": "Your invoice for March 2025"
  }
}
FieldTypeRequiredDescription
media.urlstringYesPublicly accessible HTTPS URL
media.captionstringNoCaption text
media.filenamestringNoFilename 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.
template-body-variables.json
{
  "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.

template-named-parameters.json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "order_confirmation",
    "body": {
      "variables": {
        "customer_name": "John",
        "order_id":      "ORD-12345",
        "total":         "₹1,299"
      }
    }
  }
}
template-image-header.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%"] }
  }
}
template-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"] }
  }
}
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.
template-quick-reply.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" }
    ]
  }
}
template-otp.json
{
  "to": "+919876543210",
  "type": "template",
  "template": {
    "name": "otp_message",
    "language": "en_US",
    "body": { "variables": ["784521"] },
    "copy_code": "784521"
  }
}
template-lto-image.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"
  }
}

Template fields reference

FieldTypeRequiredDescription
template.namestringYesApproved template name
template.languagestringNoLanguage code. Auto-resolved from the approved template when omitted.
template.header.typestringtext, image, video, document or location (LTO: image/video only)
template.header.urlstringMedia URL (image/video/document)
template.header.filenamestringNoDisplayed file name on the recipient device (document headers only)
template.header.variablesstring[]NoPositional values for a text-header with {{1}}, {{2}}, …
template.body.variablesstring[] | objectNoArray for positional templates, object for named-parameter templates
template.buttons[].indexnumberNo0-based button index matching the approved template
template.buttons[].sub_typestringNoquick_reply (default), url, or copy_code
template.buttons[].payloadstringNoQuick-reply payload; for copy_code this is the coupon value
template.buttons[].urlstringNoDynamic URL suffix appended to the template base URL
template.copy_codestringNoCoupon/OTP shorthand — auto-indexed after buttons[]
template.lto.expiration_time_msnumberNoLTO 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.

interactive-button.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" }
    ]
  }
}
interactive-cta-url.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"
  }
}
interactive-list.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" }
        ]
      }
    ]
  }
}
interactive-carousel.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" }
        ]
      }
    ]
  }
}
All cards in a carousel must have the same button structure (same count and types per card).

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.

GET/api/external/templates/:encodedPhoneId
GET/api/external/templates/:encodedPhoneId/:name
Requires the read_templates permission on the API key. Enable it from WhatsApp Settings → API & Webhook → API Keys.

List query parameters

ParameterTypeDescription
statusstringFilter by Meta status (default: APPROVED). Pass "all" to disable.
languagestringFilter to a single language code
searchstringCase-insensitive substring on template_name
pagenumberPage number (default: 1)
limitnumberPage size (default: 50, max: 200)
terminal
curl "https://your-api.com/api/external/templates/{phoneId}?search=invoice" \
  -H "Authorization: Bearer <your_api_token>"

Response shape

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."
}

Use this to drive the API tester

The API tester now has a built-in List templates preset. Save your token, hit Send, and you'll see exactly which templates exist on your account, in what language, with how many variables — copy-paste the name and you're ready to send.

Output

Response

success.json
{
  "success": true,
  "data": {
    "message_id": "6789abcdef1234567890abcd",
    "wa_message_id": "wamid.HBgL...",
    "conversation_id": "6789abcdef1234567890aaaa",
    "status": "sent"
  },
  "message": "Message sent successfully."
}
FieldDescription
message_idInternal WAAPI message document id
wa_message_idWhatsApp message id returned by Meta
conversation_idWAAPI conversation thread id (auto-created on first send)
statussent or failed

Edge cases

Errors

HTTPDescription
400Missing required field or invalid format
401Missing or invalid Authorization token
403Token not permitted for this phone number or message type
502Meta API rejected the message — details in error.message
error.json
{
  "success": false,
  "error": "interactive.cards is required for type=carousel."
}

Access control

Permissions

Message typeRequired permission
textsend_text
image, video, audio, documentsend_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.

message.sent
{
  "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

Set up vendor webhook endpoints from Setup → Webhooks in the dashboard, or read the Webhooks reference.