Send WhatsApp messages through two channels — WhatsApp Cloud (Meta Cloud API) and WhatsApp Lite (direct connection). This guide covers message types, template components, media, and SMS fallback.
Every customer has a durable, business-scoped user id (BSUID, alpha usernames — e.g. ZA.1021033720668862) that stays stable for your business even if the customer changes their phone number. Address a recipient by phone (to), by BSUID (customerUserId), or both — supply at least one. When both are present, to takes precedence and the BSUID rides along as a durable fallback. The phone number remains the primary identifier today — store the BSUID now and rely on it as phone numbers become optional.
Each entry in recipients accepts:
| Field | Type | Description |
|---|---|---|
to | string | Recipient phone number in international format, country code first, leading + optional (+27821234567 or 27821234567). Must not start with 0. Required unless customerUserId is supplied |
customerUserId | string | Recipient BSUID (e.g. ZA.1021033720668862), durable across phone-number changes. Supply alongside to (phone wins, BSUID kept as a fallback) or on its own to address a recipient with no phone. A malformed value is ignored, so a BSUID-only send then requires a valid to. Required unless to is supplied |
ctId | string | Client-defined tracking id, echoed back in delivery receipts and webhooks |
gstId | string | eCommunicate system tracking id for end-to-end delivery tracking |
All API requests require your Developer Key in the Authorization header. No Bearer prefix — pass the key directly. See Authentication for setup and security best practices.
| Method | Endpoint | Description |
|---|---|---|
POST | /whatsapp-api-service/v2/messages | Unified endpoint — routes to WhatsApp or SMS based on channel |
POST | /whatsapp-api-service/v2/channels/whatsapp | WhatsApp-only endpoint |
Both endpoints above deliver directly to the customer through the API Service — they do not flow through the chat service, so they will not show up on agent screens or in Message/getMessagesByParticipantId responses.
If an outbound message must be visible to your live-chat agents, send it through POST /pl4chat-service/Message/sendMessageWhatsapp (or /sendMessageWithAttachment) instead. See the Live Chat guide for the request shape, and How the system fits together for the full architecture.
| Type | Value | When to use |
|---|---|---|
| Notification | NOTIFICATION | Outbound template messages — requires approved templateName and templateId |
| Chat | CHAT | Free-form session messages within a conversation |
WhatsApp Cloud: Chat messages can only be sent within the 24-hour customer service window (starts when the customer messages you). WhatsApp Lite: Chat messages can be sent at any time — no window restrictions.
templateNameandtemplateIdare required for allNOTIFICATIONmessages- If the template body contains
{{n}}placeholders, include abodycomponent with matching parameters - Media templates require a
headercomponent withformatandurl - Button templates require a
buttoncomponent with parameters - Carousel templates require a
carouselcomponent with acardsarray
POST /whatsapp-api-service/v2/messages
{
"channel": "WHATSAPP",
"payload": {
"sender": "+27123456789",
"templateName": "seasonal_promo_text",
"templateId": "your-template-uuid",
"messageType": "NOTIFICATION",
"messages": [
{
"recipients": [{ "to": "+27987654321", "customerUserId": "ZA.1021033720668862" }],
"components": [
{
"type": "body",
"parameters": ["John"]
}
]
}
]
}
}The parameters array maps to {{1}}, {{2}}, etc. in the template body.
POST /whatsapp-api-service/v2/messages
{
"channel": "WHATSAPP",
"payload": {
"sender": "+27123456789",
"templateName": "invoice_delivery",
"templateId": "your-template-uuid",
"messageType": "NOTIFICATION",
"messages": [
{
"recipients": [{ "to": "+27987654321" }],
"components": [
{
"type": "header",
"format": "DOCUMENT",
"url": "https://example.com/invoice.pdf",
"filename": "invoice_jan.pdf"
},
{
"type": "body",
"parameters": ["John", "January"]
}
]
}
],
"fallback": {
"sendFallback": true,
"unicode": false
}
}
}Supported header formats: DOCUMENT, IMAGE, VIDEO
POST /whatsapp-api-service/v2/messages
{
"channel": "WHATSAPP",
"payload": {
"sender": "+27123456789",
"templateName": "feedback_cta",
"templateId": "your-template-uuid",
"messageType": "NOTIFICATION",
"messages": [
{
"recipients": [{ "to": "+27987654321" }],
"components": [
{
"type": "body",
"parameters": ["John"]
},
{
"type": "button",
"parameters": ["Yes", "No"],
"conversationParameters": [
{ "payload": "Yes", "conversationState": "start" },
{ "payload": "No", "conversationState": "end" }
]
}
]
}
]
}
}conversationState: "start"— keeps the conversation open for follow-upconversationState: "end"— closes the conversation when this button is tapped
POST /whatsapp-api-service/v2/messages
{
"channel": "WHATSAPP",
"payload": {
"sender": "+27123456789",
"templateName": "product_carousel_v1",
"templateId": "your-template-uuid",
"messageType": "NOTIFICATION",
"messages": [
{
"recipients": [{ "to": "+27987654321" }],
"components": [
{
"type": "body",
"parameters": ["John"]
},
{
"type": "carousel",
"cards": [
{
"components": [
{ "type": "header", "format": "IMAGE", "url": "https://example.com/product1.jpg" },
{ "type": "body", "parameters": ["Widget A", "$19.99"] },
{ "type": "button", "parameters": ["https://example.com/widget-a"] }
]
},
{
"components": [
{ "type": "header", "format": "IMAGE", "url": "https://example.com/product2.jpg" },
{ "type": "body", "parameters": ["Widget B", "$29.99"] },
{ "type": "button", "parameters": ["https://example.com/widget-b"] }
]
}
]
}
]
}
]
}
}Each card can have its own header, body parameters, and button — allowing rich product showcases.
POST /whatsapp-api-service/v2/messages
{
"channel": "WHATSAPP",
"payload": {
"sender": "+27123456789",
"messageType": "CHAT",
"messages": [
{
"recipients": [{ "to": "+27987654321" }],
"messageText": "Hello, this is a standard session message."
}
]
}
}With media attachment:
POST /whatsapp-api-service/v2/messages
{
"channel": "WHATSAPP",
"payload": {
"sender": "+27123456789",
"messageType": "CHAT",
"messages": [
{
"recipients": [{ "to": "+27987654321" }],
"messageText": "Here is the document you requested.",
"media": {
"url": "https://example.com/document.pdf",
"caption": "Monthly Report"
}
}
]
}
}Automatically send an SMS when the WhatsApp recipient cannot be reached. Add the fallback object to any WhatsApp message. There are two modes:
- Auto fallback — uses the resolved WhatsApp template text as the SMS body
- Manual fallback — overrides with your own custom SMS text
The system converts your WhatsApp template text into the SMS body automatically:
POST /whatsapp-api-service/v2/messages
{
"channel": "WHATSAPP",
"payload": {
"sender": "+27123456789",
"templateName": "seasonal_promo_text",
"templateId": "your-template-uuid",
"messageType": "NOTIFICATION",
"messages": [
{
"recipients": [{ "to": "+27987654321" }],
"components": [
{
"type": "body",
"parameters": ["John"]
}
]
}
],
"fallback": {
"sendFallback": true,
"unicode": false
}
}
}Override the SMS text with a custom message using the text field:
POST /whatsapp-api-service/v2/messages
{
"channel": "WHATSAPP",
"payload": {
"sender": "+27123456789",
"templateName": "seasonal_promo_text",
"templateId": "your-template-uuid",
"messageType": "NOTIFICATION",
"messages": [
{
"recipients": [{ "to": "+27987654321" }],
"components": [
{
"type": "body",
"parameters": ["John"]
}
]
}
],
"fallback": {
"sendFallback": true,
"text": "Hi John, your order is ready. Check the app for details.",
"unicode": true
}
}
}| Field | Type | Description |
|---|---|---|
sendFallback | boolean | Required. true to enable SMS fallback |
text | string | Custom SMS text. If omitted (auto mode), the resolved WhatsApp template text is used |
unicode | boolean | false = GSM 7-bit (160 chars/part), true = Unicode (70 chars/part) |
SMS fallback activates when the recipient's number is not registered on WhatsApp. It does not trigger for messages that fail after being accepted by WhatsApp (e.g., template rejection, rate limits).
| Component | Type | Required when | Example |
|---|---|---|---|
body | Text parameters ["val1", "val2"] | Template has {{n}} placeholders | {"type": "body", "parameters": ["John"]} |
header | Media with format + url | Template has a media header | {"type": "header", "format": "IMAGE", "url": "..."} |
button | Button parameters | Template has dynamic buttons | {"type": "button", "parameters": ["Yes"]} |
carousel | Array of cards | Template is a carousel | {"type": "carousel", "cards": [...]} |
All messaging endpoints return 202 Accepted on success:
{
"response": "Request Accepted for Processing",
"batchId": "batch-uuid",
"reqObject": { "..." : "..." }
}A 202 means the message is queued — not yet delivered. Use Webhooks or the Message Reports API to track the full delivery lifecycle: ACCEPTED > SENT > DELIVERED > READ.
- Always set
ctIdon recipients for end-to-end message tracking through your system - Validate recipient identifiers — a phone number in international format (leading
+optional, never a leading0), acustomerUserId(BSUID), or both. A recipient addressed bycustomerUserIdalone needs no phone number. - Use SMS fallback for critical notifications where delivery matters more than channel
- Batch recipients into a single API call (up to the batch limit) rather than making individual calls
- Monitor delivery rates via the Analytics API and investigate any drop in delivery percentages