UC-003 — Send WhatsApp with Template
| Field | Value |
|---|---|
| ID | UC-003 |
| Goal | Send a WhatsApp Business message via a Meta-approved template |
| Channel | |
| Complexity | Basic |
| Estimated time | 15 minutes |
| APIs involved | POST /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:
- Active API Key → How to get one
- Sufficient credit → Check in the Qlara Dashboard
- WhatsApp
phoneNumberId+ Meta-approved template
:::tip 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://lora-api.agiletelecom.com/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,
"name": "order_shipped",
"language": "it",
"category": "UTILITY",
"status": "APPROVED",
"components": [
{
"type": "HEADER",
"format": "IMAGE"
},
{
"type": "BODY",
"text": "Ciao {{1}}, il tuo ordine {{2}} e stato spedito! Tracking: {{3}}"
},
{
"type": "FOOTER",
"text": "FashionStore - Assistenza clienti"
},
{
"type": "BUTTONS",
"buttons": [
{
"type": "URL",
"text": "Traccia spedizione",
"url": "https://fashionstore.it/tracking/{{1}}"
}
]
}
]
},
{
"id": 58,
"name": "appointment_reminder",
"language": "it",
"category": "UTILITY",
"status": "APPROVED",
"components": [
{
"type": "BODY",
"text": "Gentile {{1}}, le ricordiamo l'appuntamento del {{2}} alle ore {{3}} presso {{4}}."
}
]
}
]
}
Behind the scenes — Meta template categories
Meta classifies templates into three categories with different rules and costs:
| Category | Use | Examples |
|---|---|---|
| UTILITY | Transactional messages requested by the user | Order confirmations, shipping updates, reminders |
| AUTHENTICATION | OTP codes and identity verification | Access codes, two-factor verification |
| MARKETING | Promotional communications | Offers, 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://lora-api.agiletelecom.com/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
Placeholders in Meta templates use the {{1}}, {{2}}, etc. syntax. 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}}
If the template has a button with a dynamic URL (e.g. https://fashionstore.it/tracking/{{1}}), the button placeholder uses the same numbering system but with a separate scope.
The mediaUrl field in the template object is used to replace the template's IMAGE header. The image must be publicly accessible via HTTPS.
:::warning 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://lora-api.agiletelecom.com/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": "Message 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"
}
Behind the scenes — WhatsApp webhooks
For WhatsApp you receive the same callback types as RCS:
DELIVERY:
{
"channel": "WHATSAPP",
"eventType": "DELIVERY",
"messageId": "b83c2f1a-9d4e-4b12-8c7f-3e5a1d2b4c6e",
"destination": "+393401234567",
"statusCode": 3,
"description": "delivered",
"eventDate": "2026-04-09T14:00:01Z"
}
READ:
{
"channel": "WHATSAPP",
"eventType": "READ",
"messageId": "b83c2f1a-9d4e-4b12-8c7f-3e5a1d2b4c6e",
"destination": "+393401234567",
"eventDate": "2026-04-09T14:02:30Z"
}
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://lora-api.agiletelecom.com/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://lora-api.agiletelecom.com/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://lora-api.agiletelecom.com/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
| Step | Action | Result |
|---|---|---|
| 1 | GET /whatsapp/templates | Template id and placeholder structure |
| 2 | POST /whatsapp/send | messageId returned, accepted: true |
| 3 | GET /messages/status/{id} | deliveryStatus: "DELIVERED", readDate present |
Next steps
- UC-001 — Send Single SMS: Basic scenario with SMS channel
- UC-002 — Send RCS Rich Card: Rich card with media and interactive buttons
- UC-004 — Check Delivery Status: Batch polling and webhook comparison
- WhatsApp Guide: Complete WhatsApp channel documentation
- WhatsApp Templates Guide: Create and manage WhatsApp templates