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:

:::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:

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://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​

StepActionResult
1GET /whatsapp/templatesTemplate id and placeholder structure
2POST /whatsapp/sendmessageId returned, accepted: true
3GET /messages/status/{id}deliveryStatus: "DELIVERED", readDate present

Next steps​