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