Skip to main content

UC-003 — Send WhatsApp with Template

FieldValue
IDUC-003
GoalSend a WhatsApp Business message via a Meta-approved template
ChannelWhatsApp
ComplexityBasic
Estimated time15 minutes
APIs involvedPOST /api/message-server/whatsapp/send, GET /api/partner-gateway/v1/messages/status/{customerMessageId}

Real-world scenarios​

  • OTP verification: BancaSicura sends a verification code to confirm the customer's identity during home banking access.
  • Appointment reminder: Studio Medico Verdi sends an appointment reminder 24 hours in advance, with date, time, and facility address.
  • Order update: FashionStore notifies the customer that the order has been shipped, with a courier tracking link.

Prerequisites​

Before you begin, make sure you have:

Test without costs

Add "simulation": true in the request body to validate the flow without actually sending messages and without consuming credit.

Template lifecycle​

The template must be approved by Meta before you can use it for sending. Approval happens only once and can take from a few minutes to 24 hours.

Step 1 — Identify the template ID​

Before sending, you need to know the id of the approved template. You can find it in the platform dashboard or via API:

curl -X GET "https://api.qlara.ai/api/message-server/whatsapp/templates?status=APPROVED&phoneNumberId=5&limit=10" \
-H "X-Api-Key: YOUR_API_KEY"

Response — Approved templates list​

{
"data": [
{
"id": 42,
"phoneNumberId": 5,
"name": "order_shipped",
"slug": "order_shipped",
"category": "UTILITY",
"lang": "it",
"status": "APPROVED",
"quality": "GREEN",
"createdAt": "2026-03-02 10:15:00.000+0000",
"updatedAt": "2026-03-02 11:40:12.000+0000",
"headerFormat": "IMAGE",
"body": "Ciao {1}, il tuo ordine {2} e stato spedito! Tracking: {3}",
"footer": "FashionStore - Assistenza clienti",
"buttons": [
{
"type": "URL",
"text": "Traccia spedizione",
"url": "https://fashionstore.it/tracking"
}
]
},
{
"id": 58,
"phoneNumberId": 5,
"name": "appointment_reminder",
"slug": "appointment_reminder",
"category": "UTILITY",
"lang": "it",
"status": "APPROVED",
"quality": "GREEN",
"createdAt": "2026-03-10 08:30:00.000+0000",
"updatedAt": "2026-03-10 09:05:47.000+0000",
"headerFormat": "IMAGE",
"body": "Gentile {1}, le ricordiamo l'appuntamento del {2} alle ore {3} presso {4}."
}
],
"page": 0,
"limit": 10,
"totalCount": 2,
"totalPages": 1
}
Behind the scenes — Meta template categories

Meta classifies templates into three categories with different rules and costs:

CategoryUseExamples
UTILITYTransactional messages requested by the userOrder confirmations, shipping updates, reminders
AUTHENTICATIONOTP codes and identity verificationAccess codes, two-factor verification
MARKETINGPromotional communicationsOffers, newsletters, event invitations

AUTHENTICATION templates have a special structure with a built-in "Copy code" button. MARKETING templates require the user to have given explicit consent (opt-in).

Step 2 — Send the message with placeholders​

Use the order_shipped template (id: 42) with filled placeholders and a header image:

curl -X POST https://api.qlara.ai/api/message-server/whatsapp/send \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"destination": "+393401234567",
"phoneNumberId": 5,
"template": {
"id": 42,
"mediaUrl": "https://cdn.fashionstore.it/orders/box-shipped.jpg"
},
"placeholders": {
"1": "Marco",
"2": "#ORD-20260409",
"3": "https://corriere.it/track/BRT789456123"
},
"enableNotification": true
}'

Response — Message accepted​

{
"messageId": "b83c2f1a-9d4e-4b12-8c7f-3e5a1d2b4c6e",
"simulation": false,
"results": {
"whatsapp": {
"accepted": true
},
"rcs": null,
"sms": null
}
}
Behind the scenes — How placeholders are resolved

Meta stores positional placeholders as {{1}}, {{2}}, etc.; the template body returned by the API shows them as {1}, {2}. In the API request, you map them with numeric keys:

  • "1": "Marco" replaces {1} in the template body
  • "2": "#ORD-20260409" replaces {2}
  • "3": "https://corriere.it/track/BRT789456123" replaces {3}

To put a tracked link in a URL button, pass its destination in the shortLinkT1 placeholder (see Template with tracked buttons).

The mediaUrl field in the template object is used to replace the template's IMAGE header (headerFormat: "IMAGE"). The image must be publicly accessible via HTTPS.

24-hour window

Template messages can be sent at any time. Free-form messages (body) can only be sent within 24 hours of the last message received from the user. Outside the window, always use a template.

Step 3 — Verify delivery​

curl -X GET "https://api.qlara.ai/api/partner-gateway/v1/messages/status/b83c2f1a-9d4e-4b12-8c7f-3e5a1d2b4c6e?channel=WHATSAPP" \
-H "X-Api-Key: YOUR_API_KEY"

Response — Delivered and read​

{
"customerMessageId": "b83c2f1a-9d4e-4b12-8c7f-3e5a1d2b4c6e",
"channel": "WHATSAPP",
"destination": "+393401234567",
"deliveryStatus": "DELIVERED",
"deliveryStatusDescription": "delivered",
"sendDate": "2026-04-09T14:00:00+02:00",
"deliveryDate": "2026-04-09T14:00:01+02:00",
"readDate": "2026-04-09T14:02:30+02:00"
}

Reading is not a separate status: once the recipient opens the message, deliveryStatus stays DELIVERED, readDate is filled in and you also receive a READ webhook event. If the recipient has switched off read receipts in WhatsApp's privacy settings, the read is not reported and readDate stays null.

Webhook events​

Behind the scenes — WhatsApp webhooks

For WhatsApp you receive the same callback types as RCS:

DELIVERY:

{
"eventType": "DELIVERY",
"channel": "WHATSAPP",
"messageId": "b83c2f1a-9d4e-4b12-8c7f-3e5a1d2b4c6e",
"destination": "+393401234567",
"statusCode": 3,
"description": "delivered",
"eventDate": "2026-04-09T14:00:01+02:00",
"price": 0.03
}

READ:

{
"eventType": "READ",
"channel": "WHATSAPP",
"messageId": "b83c2f1a-9d4e-4b12-8c7f-3e5a1d2b4c6e",
"destination": "+393401234567",
"eventDate": "2026-04-09T14:02:30+02:00"
}

Configure your endpoint in the Webhook guide.

Variants​

Template with tracked buttons​

To track clicks on template URL buttons, use the shortLinkT1 placeholder which generates a tracked link:

curl -X POST https://api.qlara.ai/api/message-server/whatsapp/send \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"destination": "+393401234567",
"phoneNumberId": 5,
"template": {
"id": 42
},
"placeholders": {
"1": "Giulia",
"2": "#ORD-20260410",
"3": "https://corriere.it/track/BRT111222333",
"shortLinkT1": "https://fashionstore.it/tracking/BRT111222333"
},
"enableNotification": true
}'

Template with header image​

If the template has an IMAGE type header, pass the mediaUrl inside the template object:

curl -X POST https://api.qlara.ai/api/message-server/whatsapp/send \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"destination": "+393471234567",
"phoneNumberId": 5,
"template": {
"id": 58,
"mediaUrl": "https://cdn.studiomedico.it/logo-reminder.png"
},
"placeholders": {
"1": "Sig. Rossi",
"2": "10 aprile 2026",
"3": "15:30",
"4": "Studio Medico Verdi - Via Garibaldi 25, Roma"
},
"enableNotification": true
}'

Send with SMS fallback​

If the recipient does not have WhatsApp, the message can automatically fall back to SMS:

curl -X POST https://api.qlara.ai/api/message-server/whatsapp/send \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"destination": "+393401234567",
"phoneNumberId": 5,
"template": {
"id": 42
},
"placeholders": {
"1": "Marco",
"2": "#ORD-20260409",
"3": "https://corriere.it/track/BRT789456123"
},
"fallbackSms": {
"sender": "FStore",
"text": "Ciao Marco, il tuo ordine #ORD-20260409 e stato spedito! Tracking: https://corriere.it/track/BRT789456123"
},
"enableNotification": true
}'

Response — SMS fallback activated​

{
"messageId": "c4d5e6f7-8901-2345-6789-abcdef012345",
"simulation": false,
"results": {
"whatsapp": {
"accepted": false,
"reasons": ["WhatsApp not available"]
},
"rcs": null,
"sms": {
"accepted": true,
"unicode": false,
"parts": 1,
"reasons": []
}
}
}

Common errors​

Template not approved​

{
"status": "fail",
"data": {
"template": "Template 42 is not in APPROVED status"
}
}

Solution: Verify that the template is in APPROVED status via the dashboard or the GET /whatsapp/templates?status=APPROVED API.

Invalid phoneNumberId​

{
"status": "fail",
"data": {
"phoneNumberId": "Phone number not found or not associated with your account"
}
}

Solution: Use GET /whatsapp/phone-numbers to get the IDs of WhatsApp Business numbers associated with your account.

Missing placeholders​

If the template requires placeholders that were not provided, the message is rejected. Verify the template structure and provide all required values.

Expected result​

StepActionResult
1GET /whatsapp/templatesTemplate id and placeholder structure
2POST /whatsapp/sendmessageId returned, accepted: true
3GET /messages/status/{id}?channel=WHATSAPPdeliveryStatus: "DELIVERED", readDate set once the message is read

Next steps​