Endpoint
POST https://app.octopods.io/api/v1/whatsapp/templates/{template_id}/messages
{template_id}is the ID of the approved WhatsApp template you want to send.Authentication:
X-Octopods-Auth: YOUR_WHATSAPP_API_KEY(see API Overview and Authentication).Content type:
application/json.
Finding a template ID
Templates must be approved on the WhatsApp channel before they can be sent. Each approved template has a numeric ID you can copy from the Octopods UI:
Open Settings → Channels and select your WhatsApp channel.
Open the Templates tab.
Click the template you want to send. The ID appears in the template details.
Request body
Field | Type | Required | Description |
| string | Yes | Recipient phone in E.164 format, e.g. |
| object or array | Conditional | Values for the template’s placeholders. Required when the template has variables. See the format details below. |
| string | Conditional | Public URL of the header media file. Required when the template has an image, video, or document header. |
| string | No | Display name to associate with the contact if one does not already exist. |
| boolean | No | Set to |
| string | No | The ID of the teammate the sent message should be attributed to. |
Message variable formats
The template determines the shape you must send.
Body-only templates. If every placeholder lives in the template body ({{1}}, {{2}}, …), send an array of strings:
{
"destination_phone": "+14155552671",
"message_variables": ["Alex", "1234"]
}
Templates with header, footer, or button placeholders. Send an object with one array per section. Include every section even if it is empty:
{
"destination_phone": "+14155552671",
"message_variables": {
"header": ["Order #1234"],
"body": ["Alex", "1234"],
"footer": [],
"buttons": ["track/1234"]
}
}
If the template has non-body placeholders and you send a plain array, the API rejects the request with error code 9.
Media headers
For templates with an image, video, or document header, provide a public URL in header_attachment_url. The URL must be reachable by Octopods’s servers and must point to a file that matches the template’s declared header type.
{
"destination_phone": "+14155552671",
"header_attachment_url": "https://example.com/invoice.pdf",
"message_variables": { "header": [], "body": ["Alex"], "footer": [], "buttons": [] }
}
Full example
curl -X POST "https://app.octopods.io/api/v1/whatsapp/templates/123/messages" \
-H "X-Octopods-Auth: YOUR_WHATSAPP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination_phone": "+14155552671",
"destination_user_name": "Alex Rivera",
"message_variables": ["Alex", "1234"],
"open_intercom_conversation": true
}'
Successful response
HTTP 201 Created:
{
"request_id": "abc123…",
"message_id": 98765,
"channel_message_id": "provider-id-here"
}
message_id— use this value withGET /api/v1/whatsapp/messages/{message_id}to poll delivery status.channel_message_id— the WhatsApp provider’s own ID.
Error codes
Errors return HTTP 400 Bad Request (or HTTP 429 for rate-limit breaches) with:
{ "request_id": "…", "error_code": N, "error": "…" }
Code | Meaning | Fix |
1 | A required argument is missing (e.g. | Include the named field. |
2 |
| Send an array for body-only templates or an object for templates with non-body placeholders. |
3 | Template does not exist or is not approved on this channel. | Re-check the template ID and confirm it is in the Approved state. |
4 | The number of variables you supplied does not match the template. | Count the placeholders in the template and supply exactly that many. |
5 |
| Use E.164 format (leading |
6 | Deprecated API key. | Copy the current key from the channel settings screen. |
7 | WhatsApp provider returned an error. | See the |
8 | Invalid header media URL. | Confirm the URL is public and the file matches the header’s declared type. |
9 | Legacy variable format — an array was sent for a template that has header/footer/button variables. | Send |
Note: Full details and recovery steps for provider errors are in Troubleshooting Failed Proactive Messages.
Rate limits
WhatsApp endpoints are rate-limited per channel. On a breach, Octopods returns HTTP 429 with a blocked_until timestamp telling you when the block lifts. Pause sends until the timestamp passes.
What’s next
