Text
Plain text 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.
GET /api/contacts/{id}/messages| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1). Page 1 is the most recent messages. |
limit | integer | Page size, 1–100. Default 50. |
before_id | string | Cursor — return the limit messages immediately older than this message UUID. Mutually exclusive with page. |
account | string | Restrict 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.
POST /api/contacts/{id}/messages{ "type": "text", "content": { "body": "Hello! How can I help you today?" }}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | text or interactive on this endpoint. Media is sent via POST /api/messages/media. |
content.body | string | Yes (for text) | The message text |
interactive | object | Yes (for interactive) | See Send Interactive Message |
reply_to_message_id | string | No | UUID of a message to quote-reply to. Must belong to the same contact, otherwise it is silently ignored. |
whatsapp_account | string | No | Name 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.
POST /api/messages/template| Field | Type | Required | Description |
|---|---|---|---|
contact_id | string | One of contact_id or phone_number | UUID of existing contact |
phone_number | string | One of contact_id or phone_number | Phone number (creates contact if not exists) |
template_name | string | One of template_name or template_id | Name of the template |
template_id | string | One of template_name or template_id | UUID of the template |
template_params | object | No | Named or positional body parameters |
header_params | object | No | Value 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_params | object | No | Dynamic URL button parameters (button index → value) |
account_name | string | No | Specific WhatsApp account to use |
Using phone number (creates contact if needed):
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.
POST /api/messages/media| Field | Type | Required | Description |
|---|---|---|---|
contact_id | text | Yes | UUID of the recipient contact |
file | file | Yes | The media file |
type | text | No | image (default), video, audio, or document |
caption | text | No | Caption text |
whatsapp_account | text | No | Account to send from; defaults to the contact's account |
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.
POST /api/contacts/{id}/messagesThe interactive.type selects the variant:
interactive.type | Fields used |
|---|---|
button | body, buttons: [{id, title}] |
list | body, button_text |
cta_url | body, button_text, url |
flow | flow_id (required), body, button_text (CTA label, default "Open"), header, first_screen |
voice_call | body, display_text, ttl_minutes |
Send a message with up to 3 quick reply buttons:
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:
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.
POST /api/contacts/{id}/messages/{message_id}/reaction| Field | Type | Required | Description |
|---|---|---|---|
emoji | string | Yes | The 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.
POST /api/contacts/{id}/mark-read{ "status": "success", "data": { "status": "ok" }}PUT /api/messages/{id}/readStream the media attached to a message. Returns the raw file bytes with the stored MIME type — not a JSON envelope.
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:
| Status | Description |
|---|---|
pending | Message queued for sending |
sent | Message sent to WhatsApp servers |
delivered | Message delivered to recipient's device |
read | Message read by recipient |
failed | Message failed to send |
received | Set 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