WhatsApp API
What is WhatsApp Business API
WhatsApp Business API allows you to send messages through Meta's WhatsApp Business platform via the Qlara APIs. It is the most widely used messaging channel in the world, with high open and engagement rates.
Messages can be sent in two modes:
- Template: messages based on Meta-approved templates. They can be sent at any time, without time restrictions. Ideal for notifications, marketing, and proactive communications.
- Free-form message (body): custom content (text, images, documents, etc.). Only usable within the 24-hour window from the last message received from the user.
:::warning 24-Hour Window Free-form messages (body) can only be sent within 24 hours of the last message received from the user. After this period, you must use a Meta-approved template. Messages sent outside the window will be rejected. :::
The system supports an automatic fallback chain: if WhatsApp cannot deliver the message, the system tries RCS and finally SMS.
Endpoint
Sending messages
POST https://lora-api.agiletelecom.com/api/message-server/whatsapp/send
Phone numbers
GET https://lora-api.agiletelecom.com/api/message-server/whatsapp/phone-numbers
Content-Type: application/json
Authentication: API Key (X-Api-Key) or Basic Auth (Authorization: Basic)
Send request fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
destination | string | Yes | – | Recipient phone number in international format (e.g. +393401234567) |
phoneNumberId | integer | Yes | – | Sender WhatsApp Business number ID (obtained from Get All Phone Numbers) |
template | object | Yes* | – | Reference to a Meta-approved template. *Mutually exclusive with body |
body | object | Yes* | – | Free-form message content. *Mutually exclusive with template |
campaignId | string | No | – | Campaign identifier to group multiple messages. Maximum 255 characters |
messageId | string | No | (auto) | Custom message identifier. If not provided, it is automatically generated (UUID) |
simulation | boolean | No | false | If true, the message is processed but not physically sent. Useful for testing |
enableNotification | boolean | No | true | If true, enables delivery and read notifications to your callback URL |
placeholders | object | No | – | Key-value map to replace placeholders in the template. E.g.: {"nome": "Mario"} replaces {nome} in the message |
scheduledDate | string | No | – | Scheduled send date and time. Format: yyyy-MM-dd HH:mm:ss.SSSZ (e.g. 2025-10-01 09:00:00.000+0000) |
fallbackRcs | object | No | – | Fallback configuration for the RCS channel (see Fallback section) |
fallbackSms | object | No | – | Fallback configuration for the SMS channel. Required if fallbackRcs is present |
Template object
:::tip Template pre-approval Templates must be approved by Meta before use. Plan ahead: the approval process can take up to 24 hours. Check the status of your templates in the Qlara platform. :::
Used to send a message based on a Meta-approved template.
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | Template ID in the system (obtained from Get All Templates) |
mediaUrl | string | No | Media URL to insert in the template header (if the template includes a header with an image or video) |
Body object
Used to send a free-form message. Must contain one and only one of the following sub-objects. Free-form messages can only be sent within the 24-hour window from the last message received from the user.
body > text
Simple text message.
| Field | Type | Required | Description |
|---|---|---|---|
body | string | Yes | Message text |
body > image
Image with optional caption.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes* | Public image URL. *Mutually exclusive with key |
key | string | Yes* | Media key on internal storage. *Mutually exclusive with url |
caption | string | No | Caption displayed below the image |
body > video
Video with optional caption.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes* | Public video URL. *Mutually exclusive with key |
key | string | Yes* | Internal media key. *Mutually exclusive with url |
caption | string | No | Caption |
body > audio
Audio message.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes* | Public audio URL. *Mutually exclusive with key |
key | string | Yes* | Internal media key. *Mutually exclusive with url |
body > document
Document (PDF, DOC, etc.) with optional filename and caption.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes* | Public document URL. *Mutually exclusive with key |
key | string | Yes* | Internal media key. *Mutually exclusive with url |
filename | string | No | Filename shown to the recipient (e.g. fattura_marzo.pdf) |
caption | string | No | Caption |
body > sticker
Sticker in WebP format.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes* | Public sticker URL (WebP format). *Mutually exclusive with key |
key | string | Yes* | Internal media key. *Mutually exclusive with url |
body > location
Geographic location displayed on a map.
| Field | Type | Required | Description |
|---|---|---|---|
latitude | string | Yes | Latitude (e.g. "45.4642") |
longitude | string | Yes | Longitude (e.g. "9.1900") |
name | string | No | Location name (e.g. "Negozio Milano Centro") |
address | string | No | Text address (e.g. "Via Roma 1, 20121 Milano") |
body > reaction
Emoji reaction to an existing message.
| Field | Type | Required | Description |
|---|---|---|---|
messageId | string | Yes | ID of the message to react to |
emoji | string | Yes | Reaction emoji |
Fallback chain
The system supports sending with an automatic fallback chain:
WhatsApp → RCS → SMS
If WhatsApp cannot deliver the message, the system tries RCS (if configured) and finally SMS.
fallbackRcs object
| Field | Type | Required | Description |
|---|---|---|---|
agentId | integer | Yes | Sender RCS agent ID |
templateId | integer | Yes* | RCS template ID. *Mutually exclusive with body |
type | string | No | RCS body type: TEXT, CARD, CAROUSEL (required if using body) |
body | object | Yes* | Inline RCS body. *Mutually exclusive with templateId |
fallbackSms object
| Field | Type | Required | Description |
|---|---|---|---|
sender | string | Yes | SMS sender (alphanumeric or numeric) |
text | string | Yes | Fallback SMS text |
Rule: If
fallbackRcsis present,fallbackSmsis required. You can configure onlyfallbackSmswithoutfallbackRcsfor a direct WhatsApp -> SMS fallback.
Tracked links
To monitor link clicks in templates, you can use the shortLinkT1 placeholder in the placeholders field:
"placeholders": {
"shortLinkT1": "https://www.example.com/landing-page"
}
The system automatically generates a tracked short link that replaces the {shortLinkT1} placeholder in the template. This allows you to monitor how many recipients clicked the link.
Response
{
"messageId": "e76614d1-4ac1-4d94-89f0-d07f1b5a190c",
"simulation": false,
"results": {
"whatsapp": {
"accepted": true
},
"rcs": null,
"sms": null
}
}
| Field | Type | Description |
|---|---|---|
messageId | string | Unique message ID. Use it to correlate delivery notifications |
simulation | boolean | true if the message was in simulation mode |
results.whatsapp.accepted | boolean | true if WhatsApp accepted the message |
results.rcs | object/null | RCS fallback result (if configured, otherwise null) |
results.sms | object/null | SMS fallback result (if configured, otherwise null) |
Important:
accepted: truemeans the message was accepted for sending, not that it was delivered. Delivery confirmation arrives via callback.
Notifications (Callback)
Delivery Notification
Message delivery notification:
{
"channel": "WHATSAPP",
"eventType": "DELIVERY",
"messageId": "88116e2f-b8b4-40fe-b727-f453b6b99adc",
"destination": "+393409997528",
"statusCode": 3,
"description": "delivered",
"eventDate": "2025-10-16T10:42:18Z"
}
| statusCode | Meaning |
|---|---|
3 | Message delivered |
6 | Message undeliverable |
Read Notification
Read notification (the recipient opened the message):
{
"channel": "WHATSAPP",
"eventType": "READ",
"messageId": "88116e2f-b8b4-40fe-b727-f453b6b99adc",
"destination": "+393409997528",
"eventDate": "2025-10-16T10:42:18Z"
}
Inbound Text Message
Notification when the user replies with a text message:
{
"channel": "WHATSAPP",
"eventType": "INBOUND",
"messageId": "wamid.HBxxxxxxxxxxxxxxxxx",
"source": "+393471488489",
"destination": "+393209998877",
"receivedDate": "2025-10-31T10:59:05.099Z",
"messageType": "TEXT",
"text": "Messaggio di risposta dell'utente"
}
Inbound Media Message
Notification when the user sends an image, audio, or video. The mediaKey field contains the key to download the file.
{
"channel": "WHATSAPP",
"eventType": "INBOUND",
"source": "+393471488489",
"destination": "+393209998877",
"receivedDate": "2025-10-31T11:00:41.003Z",
"messageType": "IMAGE",
"mediaKey": "b394ab72/efd4987c/39b4879f6fed01f0d622453be1488c93"
}
Phone Numbers
To get the list of WhatsApp Business numbers associated with your account:
GET https://lora-api.agiletelecom.com/api/message-server/whatsapp/phone-numbers
From the response, take the id field value of the number you want to use as the sender: this will be the phoneNumberId to include in send requests.
To get the details of a single number:
GET https://lora-api.agiletelecom.com/api/message-server/whatsapp/phone-numbers/{id}
Examples
1. Sending with a template
Sending a message based on an approved template, with placeholders and media in the header:
{
"destination": "+393401234567",
"phoneNumberId": 5,
"template": {
"id": 42,
"mediaUrl": "https://example.com/images/promo.jpg"
},
"placeholders": {
"nome": "Mario",
"codiceSconto": "SPRING25"
},
"enableNotification": true
}
2. Sending a text message
Sending a free-form text message (requires an open 24h window):
{
"destination": "+393401234567",
"phoneNumberId": 5,
"body": {
"text": {
"body": "Ciao Mario! Grazie per averci contattato. Il tuo ordine è in fase di preparazione."
}
}
}
3. Sending an image
Sending an image with a caption:
{
"destination": "+393401234567",
"phoneNumberId": 5,
"body": {
"image": {
"url": "https://example.com/images/ricevuta-ordine.jpg",
"caption": "Ecco la ricevuta del tuo ordine #12345"
}
}
}
4. Sending a document
Sending a PDF document with a custom filename:
{
"destination": "+393401234567",
"phoneNumberId": 5,
"body": {
"document": {
"url": "https://example.com/docs/fattura-marzo-2025.pdf",
"filename": "Fattura_Marzo_2025.pdf",
"caption": "In allegato la fattura di marzo 2025"
}
},
"campaignId": "fatture-marzo-2025"
}
5. Sending with RCS + SMS fallback
Sending with a complete fallback chain: WhatsApp -> RCS -> SMS. If WhatsApp fails to deliver, the system tries RCS; if RCS also fails, it sends an SMS.
{
"destination": "+393401234567",
"phoneNumberId": 5,
"template": {
"id": 42
},
"placeholders": {
"nome": "Mario"
},
"enableNotification": true,
"fallbackRcs": {
"agentId": 1,
"templateId": 10
},
"fallbackSms": {
"sender": "MyBrand",
"text": "Ciao Mario, abbiamo una promozione per te! Visita il nostro sito per i dettagli."
}
}
Response (with fallback activated):
{
"messageId": "e76614d1-4ac1-4d94-89f0-d07f1b5a190c",
"simulation": false,
"results": {
"whatsapp": {
"accepted": true
},
"rcs": {
"accepted": true,
"reasons": []
},
"sms": {
"accepted": true,
"unicode": false,
"parts": 1,
"reasons": []
}
}
}
Error codes
| HTTP Code | Meaning | What to do |
|---|---|---|
200 | Request processed | Check results.whatsapp.accepted to verify acceptance |
400 | Bad request | Verify required fields (destination, phoneNumberId, body or template) |
401 | Unauthenticated | Verify your API Key or Basic Auth credentials |
403 | Access denied | IP not whitelisted or unauthorized resource |
404 | Not found | Template or number does not exist |
422 | Unprocessable entity | Data is syntactically correct but semantically invalid (e.g. unapproved template) |
429 | Too many requests | You have exceeded the rate limit. Retry after the indicated period |
500 | Internal server error | Retry later. If the problem persists, contact support |