The send envelope
These three optional fields are accepted by every POST /send-* route:
| Field | Type | Effect |
|---|---|---|
quotedMessageId | string | Send as a reply to that message. WhatsApp renders the quote from its own copy; with the hosted inbox we also fill the preview. |
mentions | string[] | User-parts (LID or phone digits) to @-mention. Put @<user-part> in the text so clients highlight it. |
metaData | object | Any JSON, travels with the message and comes back as meta on the fromMe webhook and in hosted history. {"fromAI": true} also flags the stored row. |
to accepts a LID (123456789012345), a phone with + (+60123456789), or a full JID including groups (…@g.us). See LIDs vs phone numbers.
Text, replies, mentions
POST https://api.meowsapp.com/send-message
{
"clientId": "acmemain",
"to": "123456789012345",
"body": "Hi @60111111111111 — the unit on level 12 is still available.",
"quotedMessageId": "3EB0C4D5E6F7",
"mentions": ["60111111111111"],
"metaData": { "fromAI": true, "userId": "flow_sales_v2" }
}Typing indicator
Show "typing…" before a reply for a natural feel, especially from bots. It clears itself when the message is sent.
POST https://api.meowsapp.com/send-typing { "clientId": "acmemain", "to": "123456789012345", "composing": true }Polls
POST https://api.meowsapp.com/send-poll-message
{ "clientId": "acmemain", "to": "123456789012345", "pollName": "Which slot suits you?", "pollOptions": ["Sat 3 pm", "Sun 11 am", "Neither"], "selectableCount": 1 }
// vote → webhook type "vote_update", pollVote: { "pollMessageId": "…", "selectedNames": ["Sat 3 pm"], "resolved": true }Store the options you sent against the returned messageId, or configure a pollLookupUrl, so votes come back resolved to names — see poll votes.
Buttons
POST https://api.meowsapp.com/send-interactive-message
{
"clientId": "acmemain",
"to": "123456789012345",
"bodyText": "Confirm your viewing on Saturday, 3 pm?",
"footerText": "Acme Realty",
"buttons": [
{ "type": "quick_reply", "displayText": "Confirm", "id": "confirm" },
{ "type": "quick_reply", "displayText": "Reschedule", "id": "reschedule" },
{ "type": "cta_url", "displayText": "Directions", "url": "https://maps.example.com/site" }
]
}
// tap → webhook type "interactive_reply", interactive: { "selectedId": "confirm", "selectedDisplayText": "Confirm" }type | Required fields | Behaviour |
|---|---|---|
quick_reply | displayText, id | Sends id back as an interactive_reply event. |
cta_url | displayText, url | Opens the link. |
cta_call | displayText, phoneNumber | Starts a phone call. |
cta_copy | displayText, id, copyCode | Copies copyCode to the clipboard. |
Buttons render differently across WhatsApp versions
Some older clients show interactive messages as plain text. Design flows so a typed reply ("1", "confirm") works too.
Reactions & edits
POST https://api.meowsapp.com/send-reaction { "clientId": "acmemain", "to": "123456789012345", "messageId": "3EB0…", "emoji": "👍" }
POST https://api.meowsapp.com/send-reaction { "clientId": "acmemain", "to": "123456789012345", "messageId": "3EB0…", "emoji": "" } // remove
POST https://api.meowsapp.com/edit-message { "clientId": "acmemain", "to": "123456789012345", "messageId": "3EB0…", "body": "Saturday, 4 pm" }Edits work on your own text messages for roughly 15 minutes after sending (WhatsApp's rule). Previous text is kept in hosted history under extras.editHistory.
Locations & contact cards
POST https://api.meowsapp.com/send-location { "clientId": "acmemain", "to": "123456789012345", "latitude": 3.1579, "longitude": 101.7116, "name": "Acme Gallery", "address": "Jalan Ampang, KL" }
POST https://api.meowsapp.com/send-contact { "clientId": "acmemain", "to": "123456789012345", "displayName": "Acme Sales", "phone": "+60123456789" }Deleting
POST https://api.meowsapp.com/delete-message
{ "clientId": "acmemain", "to": "123456789012345", "messageId": "3EB0…", "fromMe": true, "mode": "everyone" }mode: "everyone" revokes for all participants (only your own messages — WhatsApp enforces it). mode: "me" removes it from this device and from hosted history.
Attributing sends (AI vs human)
When several systems share one number — an AI agent, a human inbox, a notification pipeline — put the origin in metaData. It is embedded in the message itself, so the fromMe webhook (and hosted history) can tell them apart with no joins:
"metaData": { "fromAI": true, "userId": "flow_sales_v2", "sender": "Acme Assistant", "campaign": "aug-launch" }Responses & partial success
{ "status": "sent", "messageId": "3EB0A1B2C3D4E5F6" }
{ "status": "sent", "messageId": "3EB0…", "warning": "sent but failed to save to inbox: …" } // delivered; only hosted history failed| Status | Meaning | Retry? |
|---|---|---|
200 | Delivered to WhatsApp. | — |
400 | Missing/invalid fields. | No — fix the request. |
404 | Unknown or unpaired clientId. | No — check /check-state, re-pair if needed. |
502 | WhatsApp rejected it or the session is offline. Nothing was delivered. | Yes, with backoff. |