Skip to content

Messages

The Messages API allows you to send various types of WhatsApp messages including text, media, templates, and interactive messages.

Retrieve messages for a specific contact.

Terminal window
GET /api/contacts/{id}/messages
ParameterTypeDescription
pageintegerPage number (default: 1). Page 1 is the most recent messages.
limitintegerPage size, 1–100. Default 50.
before_idstringCursor — return the limit messages immediately older than this message UUID. Mutually exclusive with page.
accountstringRestrict to one WhatsApp account.

Messages are always returned in chronological order (oldest first) within the page.

{
"status": "success",
"data": {
"messages": [
{
"id": "uuid",
"contact_id": "uuid",
"direction": "incoming",
"message_type": "text",
"content": { "body": "Hello!" },
"media_url": "",
"media_mime_type": "",
"media_filename": "",
"interactive_data": null,
"status": "delivered",
"wamid": "wamid.xxx",
"error_message": "",
"is_reply": false,
"reply_to_message_id": null,
"reply_to_message": null,
"reactions": [],
"whatsapp_account": "Main Account",
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
}
],
"total": 100,
"page": 1,
"limit": 50,
"has_more": true
}
}

The field is message_type (not type), the WhatsApp ID is wamid (not wa_message_id), and timestamps are created_at / updated_at (there is no timestamp). A before_id request returns messages, total and has_more only — no page/limit.

Send a text message to a contact.

Terminal window
POST /api/contacts/{id}/messages
{
"type": "text",
"content": {
"body": "Hello! How can I help you today?"
}
}
FieldTypeRequiredDescription
typestringYestext or interactive on this endpoint. Media is sent via POST /api/messages/media.
content.bodystringYes (for text)The message text
interactiveobjectYes (for interactive)See Send Interactive Message
reply_to_message_idstringNoUUID of a message to quote-reply to. Must belong to the same contact, otherwise it is silently ignored.
whatsapp_accountstringNoName of the WhatsApp account to send from; defaults to the contact's account

Returns the created message object (same shape as in the list response above):

{
"status": "success",
"data": {
"id": "uuid",
"contact_id": "uuid",
"direction": "outgoing",
"message_type": "text",
"content": { "body": "Hello! How can I help you today?" },
"status": "sent",
"wamid": "wamid.xxx",
"whatsapp_account": "Main Account",
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
}
}

Send a pre-approved template message.

Terminal window
POST /api/messages/template
FieldTypeRequiredDescription
contact_idstringOne of contact_id or phone_numberUUID of existing contact
phone_numberstringOne of contact_id or phone_numberPhone number (creates contact if not exists)
template_namestringOne of template_name or template_idName of the template
template_idstringOne of template_name or template_idUUID of the template
template_paramsobjectNoNamed or positional body parameters
header_paramsobjectNoValue for a TEXT header with a {{var}} (max 1). Keyed by the variable's name for named templates, or "1" for positional. Falls back to template_params if omitted.
button_paramsobjectNoDynamic URL button parameters (button index → value)
account_namestringNoSpecific WhatsApp account to use

Using phone number (creates contact if needed):

Terminal window
curl -X POST "http://your-server:8080/api/messages/template" \
-H "X-API-Key: whm_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "919876543210",
"template_name": "hello_world"
}'
{
"phone_number": "919876543210",
"template_name": "hello_world"
}

With named parameters:

If your template has placeholders like Hello {{name}}, your order {{order_id}} is ready:

{
"contact_id": "uuid",
"template_name": "order_confirmation",
"template_params": {
"name": "John",
"order_id": "12345"
}
}

With positional parameters:

{
"phone_number": "919876543210",
"template_name": "order_confirmation",
"template_params": {
"1": "John",
"2": "12345"
}
}

With a TEXT header variable:

Meta allows at most one variable in a TEXT header. If your template's header is Our {{season}} sale is on!, supply the value via header_params:

{
"phone_number": "919876543210",
"template_name": "seasonal_promotion",
"header_params": {
"season": "Summer"
},
"template_params": {
"customer_name": "John",
"discount": "25%"
}
}

For a positional header (Our {{1}} sale is on!), use "1" as the key. If you omit header_params, the value is looked up in template_params by the same name — convenient for named templates where the header variable doesn't collide with a body variable.

With URL button parameters:

If your template has a URL button with a dynamic variable (e.g., https://example.com/track/{{1}}), provide the dynamic value via button_params. The key is the button index (starting from "0"):

{
"contact_id": "uuid",
"template_name": "order_shipped",
"template_params": {
"name": "John",
"order_id": "12345"
},
"button_params": {
"0": "12345"
}
}
{
"status": "success",
"data": {
"message_id": "uuid",
"phone_number": "919876543210",
"status": "pending",
"template_name": "order_confirmation"
}
}

Upload and send an image, video, document, or audio message.

Terminal window
POST /api/messages/media
FieldTypeRequiredDescription
contact_idtextYesUUID of the recipient contact
filefileYesThe media file
typetextNoimage (default), video, audio, or document
captiontextNoCaption text
whatsapp_accounttextNoAccount to send from; defaults to the contact's account
Terminal window
curl -X POST "http://your-server:8080/api/messages/media" \
-H "X-API-Key: whm_your_api_key" \
-F "contact_id=<uuid>" \
-F "type=image" \
-F "caption=Check out this product!" \
-F "file=@./product.jpg"

Meta's own size limits apply (roughly 5 MB images, 16 MB audio/video, 100 MB documents); Whatomate does not enforce its own cap.

Returns the same message object shape as the other send endpoints:

{
"status": "success",
"data": {
"id": "uuid",
"contact_id": "uuid",
"direction": "outgoing",
"message_type": "image",
"content": { "body": "Check out this product!" },
"media_url": "images/…",
"media_mime_type": "image/jpeg",
"media_filename": "product.jpg",
"status": "sent",
"whatsapp_account": "Main Account",
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
}
}

Send interactive messages with buttons, CTA URLs, WhatsApp Flows, or a Call button.

Terminal window
POST /api/contacts/{id}/messages

The interactive.type selects the variant:

interactive.typeFields used
buttonbody, buttons: [{id, title}]
listbody, button_text
cta_urlbody, button_text, url
flowflow_id (required), body, button_text (CTA label, default "Open"), header, first_screen
voice_callbody, display_text, ttl_minutes

Send a message with up to 3 quick reply buttons:

Terminal window
curl -X POST "http://your-server:8080/api/contacts/{contact_id}/messages" \
-H "X-API-Key: whm_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"type": "interactive",
"interactive": {
"type": "button",
"body": "How would you like to proceed?",
"buttons": [
{ "id": "yes", "title": "Yes" },
{ "id": "no", "title": "No" }
]
}
}'
{
"type": "interactive",
"interactive": {
"type": "button",
"body": "How would you like to proceed?",
"buttons": [
{ "id": "yes", "title": "Yes" },
{ "id": "no", "title": "No" }
]
}
}

Send a message with a call-to-action URL button:

Terminal window
curl -X POST "http://your-server:8080/api/contacts/{contact_id}/messages" \
-H "X-API-Key: whm_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"type": "interactive",
"interactive": {
"type": "cta_url",
"body": "Click below to view your order details",
"button_text": "View Order",
"url": "https://example.com/orders/12345"
}
}'
{
"type": "interactive",
"interactive": {
"type": "cta_url",
"body": "Click below to view your order details",
"button_text": "View Order",
"url": "https://example.com/orders/12345"
}
}
{
"type": "interactive",
"interactive": {
"type": "flow",
"flow_id": "<meta_flow_id>",
"header": "Order form",
"body": "Tap below to place your order",
"button_text": "Open form",
"first_screen": "WELCOME"
}
}

The flow must belong to your organization, otherwise 400 Flow not found for this organization. Omitting flow_id returns 400 flow_id is required to send a flow.

{
"type": "interactive",
"interactive": {
"type": "voice_call",
"body": "Need help? Call us.",
"display_text": "Call support",
"ttl_minutes": 60
}
}

Requires the sending account to have Business Calling enabled, otherwise the request is rejected with 400. When the recipient taps the button, the resulting inbound call is sticky-routed back to the agent who sent it — the routing payload is stamped server-side from the authenticated user and is never read from the request body.

Returns the created message object. interactive_data carries the rendered interactive payload.

{
"status": "success",
"data": {
"id": "uuid",
"contact_id": "uuid",
"direction": "outgoing",
"message_type": "interactive",
"content": { "body": "How would you like to proceed?" },
"interactive_data": { },
"status": "sent",
"is_reply": false,
"whatsapp_account": "Main Account",
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
}
}

Add or remove an emoji reaction on a specific message.

Terminal window
POST /api/contacts/{id}/messages/{message_id}/reaction
FieldTypeRequiredDescription
emojistringYesThe emoji to react with. Send an empty string ("") to remove your existing reaction.
{
"emoji": "👍"
}

Each user may have only one reaction per message; sending a new emoji replaces the previous one from the same user.

{
"status": "success",
"data": {
"message_id": "uuid",
"reactions": [
{
"emoji": "👍",
"from_user": "user-uuid"
}
]
}
}

Mark all incoming messages from a contact as read. When the account has auto read receipts enabled, read receipts are also sent to WhatsApp.

Terminal window
POST /api/contacts/{id}/mark-read
{
"status": "success",
"data": {
"status": "ok"
}
}
Terminal window
PUT /api/messages/{id}/read

Stream the media attached to a message. Returns the raw file bytes with the stored MIME type — not a JSON envelope.

Terminal window
GET /api/media/{message_id}

Returns 404 No media found when the message has no attachment. Callers without contacts:read may only fetch media for contacts assigned to them, or for contacts with an active transfer to a team they belong to — otherwise 403 Access denied.

Messages go through the following status flow:

StatusDescription
pendingMessage queued for sending
sentMessage sent to WhatsApp servers
deliveredMessage delivered to recipient's device
readMessage read by recipient
failedMessage failed to send
receivedSet on inbound messages

Valid message_type values are text, image, video, audio, document, template, interactive, flow, reaction, location, and contact.

Text

Plain text messages

Image

JPEG, PNG images with optional caption

Video

MP4 videos with optional caption

Document

PDF, Word, Excel, and other documents

Audio

Voice messages and audio files

Template

Pre-approved message templates

Interactive

Buttons, lists, and reply buttons

Flow

WhatsApp Flows