Send WABA Message
Automate this endpoint with an AI agent using the 2chat-whatsapp-waba skill:
npx skills add 2ChatCo/agent-skills -s 2chat-whatsapp-waba
Send a message via WhatsApp Business API (WABA). You can send either a template message (for starting a conversation or after the 24-hour window) or a session message (plain text within an active conversation).
Requires a WABA channel connected to your 2Chat account. Get template UUIDs from the Get WABA Templates endpoint.
Request body (common fields)
| Field | Description | Required |
|---|---|---|
to_number | Recipient number in E.164 format (e.g. +595981048477). | Yes |
from_number | Your WABA number in E.164 format (e.g. +5215512345432). | Yes |
For template messages, also send:
| Field | Description | Required |
|---|---|---|
template_uuid | Template UUID from Get WABA Templates. | Yes |
params | Template variable values. Required for template messages; use an empty object {} (or e.g. {"body": []}) if the template has no variables. | Yes |
params.body | Array of strings replacing {{1}}, {{2}}, … in the template body. | If template has body variables |
params.header | Array of strings replacing {{1}}, {{2}}, … in a TEXT header. Only for templates whose header is TEXT with placeholders. | If applicable |
params.header_media_url | Public URL to the image, video, or document for templates with an IMAGE / VIDEO / DOCUMENT header. Must be http or https and publicly downloadable. 2Chat fetches the file and re-hosts it on its own storage before forwarding to WhatsApp, so short-lived signed URLs are fine as long as they are reachable at send time. | If template has a media header |
params.header_media_filename | Optional filename hint for DOCUMENT templates (e.g. invoice.pdf). | No |
For session messages (within 24-hour window), send:
| Field | Description | Required |
|---|---|---|
text | Plain text message. | Yes |
You must send either a template message (template_uuid and params; params is required even if empty) or a session message (text), not both in the same request.
Send a template message
Use when starting a conversation or when the 24-hour session has expired. Replace template placeholders with values in params.body (and optionally params.header or params.buttons).
- cURL
- Python
- JavaScript
curl --request POST \
--url 'https://api.p.2chat.io/open/waba/send-message' \
--header 'Content-Type: application/json' \
--header 'X-User-API-Key: your_api_key_here' \
--data '{
"to_number": "+595981048477",
"from_number": "+5215512345432",
"template_uuid": "TMP1b44a079-c75c-4403-bc8f-a75c4ce5cd23",
"params": {
"body": ["Maria", "TRK-98452"]
}
}'
import requests
import json
url = "https://api.p.2chat.io/open/waba/send-message"
payload = {
"to_number": "+595981048477",
"from_number": "+5215512345432",
"template_uuid": "TMP1b44a079-c75c-4403-bc8f-a75c4ce5cd23",
"params": {
"body": ["Maria", "TRK-98452"]
}
}
headers = {
"Content-Type": "application/json",
"X-User-API-Key": "your_api_key_here"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
const axios = require('axios');
axios.post('https://api.p.2chat.io/open/waba/send-message', {
to_number: '+595981048477',
from_number: '+5215512345432',
template_uuid: 'TMP1b44a079-c75c-4403-bc8f-a75c4ce5cd23',
params: {
body: ['Maria', 'TRK-98452']
}
}, {
headers: {
'Content-Type': 'application/json',
'X-User-API-Key': 'your_api_key_here'
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error));
Send a template with a media header
Use this shape when the template's header is IMAGE, VIDEO, or DOCUMENT. Pass the public URL of the media in params.header_media_url. The type (image / video / document) is derived from the template — you do not need to specify it.
- cURL
- Python
- JavaScript
curl --request POST \
--url 'https://api.p.2chat.io/open/waba/send-message' \
--header 'Content-Type: application/json' \
--header 'X-User-API-Key: your_api_key_here' \
--data '{
"to_number": "+595981048477",
"from_number": "+5215512345432",
"template_uuid": "TMP1b44a079-c75c-4403-bc8f-a75c4ce5cd23",
"params": {
"body": ["Maria"],
"header_media_url": "https://example.com/promo.png"
}
}'
import requests
url = "https://api.p.2chat.io/open/waba/send-message"
payload = {
"to_number": "+595981048477",
"from_number": "+5215512345432",
"template_uuid": "TMP1b44a079-c75c-4403-bc8f-a75c4ce5cd23",
"params": {
"body": ["Maria"],
"header_media_url": "https://example.com/promo.png"
}
}
headers = {
"Content-Type": "application/json",
"X-User-API-Key": "your_api_key_here"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
const axios = require('axios');
axios.post('https://api.p.2chat.io/open/waba/send-message', {
to_number: '+595981048477',
from_number: '+5215512345432',
template_uuid: 'TMP1b44a079-c75c-4403-bc8f-a75c4ce5cd23',
params: {
body: ['Maria'],
header_media_url: 'https://example.com/promo.png'
}
}, {
headers: {
'Content-Type': 'application/json',
'X-User-API-Key': 'your_api_key_here'
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error));
For DOCUMENT templates, you can also pass params.header_media_filename to control the filename shown to the recipient (e.g. "header_media_filename": "invoice.pdf"). For VIDEO templates, the request shape is identical to IMAGE — only params.header_media_url is needed.
- The URL must use
httporhttpsand resolve to a publicly reachable host. URLs pointing to private ranges (loopback, link-local, RFC1918) are rejected. - 2Chat downloads the file and re-hosts it on its own storage before sending to WhatsApp, so hot-link-protected CDNs work as long as the file can be fetched server-side.
- The same hosted asset is reused across sends, so passing identical URLs is cheap.
Send a session message
Use when replying within the 24-hour customer session. Send only text (no template_uuid or params).
- cURL
- Python
- JavaScript
curl --request POST \
--url 'https://api.p.2chat.io/open/waba/send-message' \
--header 'Content-Type: application/json' \
--header 'X-User-API-Key: your_api_key_here' \
--data '{
"to_number": "+595981048477",
"from_number": "+5215512345432",
"text": "Hi, how can we help you today?"
}'
import requests
import json
url = "https://api.p.2chat.io/open/waba/send-message"
payload = {
"to_number": "+595981048477",
"from_number": "+5215512345432",
"text": "Hi, how can we help you today?"
}
headers = {
"Content-Type": "application/json",
"X-User-API-Key": "your_api_key_here"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
const axios = require('axios');
axios.post('https://api.p.2chat.io/open/waba/send-message', {
to_number: '+595981048477',
from_number: '+5215512345432',
text: 'Hi, how can we help you today?'
}, {
headers: {
'Content-Type': 'application/json',
'X-User-API-Key': 'your_api_key_here'
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error));
Response
Example success response:
{
"success": true,
"batched": true,
"message_uuid": "MSG126d9055-9dad-415f-b934-ff909773a8ef"
}
| Field | Description |
|---|---|
success | true when the message was accepted |
batched | true when the message was successfully queued for sending |
message_uuid | Internal UUID of the message for tracking |
Error responses
Errors return an HTTP 4xx status and a JSON body in the shape:
{
"error": true,
"error_message": "Template '...' has a IMAGE header. You must pass 'header_media_url' with a public URL to the media."
}
Common cases:
| Status | error_message | When |
|---|---|---|
400 | from_number is required | Missing required field |
400 | to_number is required | Missing required field |
400 | template_uuid or text is required | Neither is present |
400 | template_uuid and text cannot be used together | Both are present |
400 | `params` must be an object | params is not a JSON object |
400 | `params.header_media_url` must be a valid public URL | URL has wrong type or invalid syntax |
400 | Template '...' has a IMAGE header. You must pass 'header_media_url' ... | Template expects media, caller did not pass header_media_url |
400 | Template '...' has no media header. Do not pass 'header_media_url'. | Template is TEXT-only and caller included header_media_url |
400 | Could not fetch the media URL provided in 'params.header_media_url' ... | URL unreachable, blocked by the SSRF guard (private/loopback host), or rejected by the scheme allowlist |
400 | from_number does not exist on your account | Number not found or marked for deletion |
400 | Your WABA number (...) is not connected to 2Chat | Channel is in a non-connected state |