Sending messages
Send text, polls and contact cards, reply, mention, react, delete, mark read, show presence.
Media is on its own page, Media. Placeholders in examples: channel <CHANNEL_ID>, token <CHANNEL_TOKEN>, number 66XXXXXXXXX, message 3EB0C767D26A1D8E2A41, base https://API_ORIGIN/v1.
POST/v1/sessions/<CHANNEL_ID>/messages/text
Send a text, with optional mentions and a quoted reply.
POST/v1/sessions/<CHANNEL_ID>/messages/contact
Send a contact card.
POST/v1/sessions/<CHANNEL_ID>/messages/poll
Send a poll.
PUT/v1/sessions/<CHANNEL_ID>/messages/<MESSAGE_ID>/reaction
React to a message.
DELETE/v1/sessions/<CHANNEL_ID>/messages/<MESSAGE_ID>/reaction
Remove your reaction.
DELETE/v1/sessions/<CHANNEL_ID>/messages/<MESSAGE_ID>
Delete a message you sent, for everyone.
POST/v1/sessions/<CHANNEL_ID>/messages/read
Send read receipts.
POST/v1/sessions/<CHANNEL_ID>/presence
Set the channel's presence.
POST/v1/sessions/<CHANNEL_ID>/typing
Show or clear the typing indicator.
What every send returns
Sends answer 200 with:
{
"id": "3EB0C767D26A1D8E2A41",
"chat_jid": "[email protected]",
"timestamp": "2026-09-17T03:00:00Z",
"status": "server_ack"
}ididentifies the message. Keep it: you need it to quote, react, delete, and to matchmessage.statuswebhooks.server_ackmeans WhatsApp's servers accepted it. Whether it reached the phone, and whether it was read, arrives asmessage.statuswebhooks (delivered,read,played,undecryptable).- The API has no idempotency key. If a send returns 504
send_unconfirmed, the message may have gone out. Treat it as possibly delivered and do not resend blindly. Theidin that error names a log row marked ambiguous. It is not a WhatsApp message ID. See Recipes. - An API send is not echoed back to you as a
messagewebhook. - Every send, poll, reaction and delete counts against your daily send cap, and against the sandbox allowance on a sandbox channel.
Send a text
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/messages/text" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"to": "66XXXXXXXXX", "text": "Thanks, see you tomorrow"}'Fields:
to(required): phone number with country code, or a JID.text: the message.mentions(optional): phone numbers or user JIDs of the people to mention. Group and other non-person addresses are refused with 400.quoted(optional): the ID of a message this channel has in the same chat. The text is sent as a reply to it. Letters, digits, dot, underscore and hyphen, 1 to 128 characters.
Reply and mention in one call:
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/messages/text" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"to": "[email protected]", "text": "@66XXXXXXXXX see above", "mentions": ["66XXXXXXXXX"], "quoted": "3EB0C767D26A1D8E2A41"}'Put @ and the number in the text where the tag should appear. The mentions list notifies the person. Without the @number in the text, they are notified but nothing is highlighted.
Errors specific to this route: 400 quote_not_found (the quoted message is not in this channel's log), 400 quote_wrong_chat (it belongs to another chat).
Send a poll
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/messages/poll" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"to": "66XXXXXXXXX", "question": "Which day suits you?", "options": ["Monday", "Tuesday", "Wednesday"], "multiple": false}'questionup to 255 characters.options: 2 to 12 different options, each up to 100 characters.multiple:truelets a person choose more than one option. The default isfalse.
Votes arrive as message webhooks of type poll_vote, with the chosen options by name. See Webhook events.
Send a contact card
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/messages/contact" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"to": "66XXXXXXXXX", "display_name": "Customer A", "vcard": "BEGIN:VCARD\nVERSION:3.0\nFN:Customer A\nTEL;type=CELL;waid=66XXXXXXXXX:+66XXXXXXXXX\nEND:VCARD"}'to, display_name and vcard are all required.
React to a message
curl -X PUT "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/messages/3EB0C767D26A1D8E2A41/reaction" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"emoji": "馃憤"}'- Send exactly one emoji. The value must be at most 32 bytes of valid UTF-8, include at least one non-ASCII character, and contain no spaces, control characters, ASCII letters or digits.
okis refused with 400. The gateway does not check that the value is a single emoji, so send exactly one. - A new reaction replaces this channel's earlier one on that message.
- A message this channel has no record of answers 404
message_not_found. - Remove your reaction with
DELETEon the same path, no body.
Delete a message for everyone
curl -X DELETE "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/messages/3EB0C767D26A1D8E2A41" \
-H "Authorization: Bearer <CHANNEL_TOKEN>"{
"message_id": "3EB0C767D26A1D8E2A41",
"chat_jid": "[email protected]",
"id": "3EB0C767D26A1D8E2A42",
"timestamp": "2026-09-17T03:01:00Z",
"status": "server_ack"
}- Only your own messages, sent through the API or on the phone. Someone else's answers 403
not_own_message. Deleting other people's messages as a group admin is not offered. - Within 48 hours of sending. An older message answers 409
revoke_window_passed. - A message not in the log answers 404
message_not_found. - Deleting again is safe: it answers 200 with
status: "already_revoked"and sends nothing. In that caseidandtimestampare absent. A second delete while the first is running answers 409revoke_in_progress. - The log keeps the message and marks it with
revoked_at. A delete made on the phone, or by the sender of a message you received, arrives as amessagewebhook of typeprotocolwithrevoke.message_id.
Mark messages read
Mark a whole chat read:
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/messages/read" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"chat": "[email protected]"}'{ "status": "read", "receipted": 3 }Or mark named messages:
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/messages/read" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"chat": "[email protected]", "message_ids": ["3EB0C767D26A1D8E2A41"], "up_to": true}'- Send
message_ids(at most 500), or send onlychatto mark the whole chat.senderis also accepted. - A whole-chat read that finds nothing unread answers 200 with
{"status":"nothing_to_acknowledge","receipted":0}. - In a group, only the ids you pass are marked, and each sender sees only their own.
up_to: truealso marks every unread message in the group at or before the newest id you pass, each to its own sender. A request that would mark 500 or more messages this way, or send receipts to more than 5 senders, answers 400up_to_range_too_largeand sends nothing.up_toworks only in a group chat and only withmessage_ids. skippedlists ids you sent that were not marked: ids this channel has no record of, when the request does not settle which single chat they belong to.- If a read fails after some receipts went out, the answer is 502
receipt_partialwithreceipted. Do not resend the request as is.
Presence and typing
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/presence" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"presence": "available"}'
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/typing" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"chat": "[email protected]", "state": "typing"}'Both answer {"status":"ok"}.
presence:availableorunavailable.onlineandofflineare accepted as other names.state:typing(orcomposing),recording, orpaused(orstop, which clears it).
What can go wrong on a send
| Status and code | Meaning | What to do |
|---|---|---|
409 not_linked | no phone is linked | link it in the console |
409 stood_down | WAgate paused the number after WhatsApp pushed back | reconnect in the console once the restriction ends |
403 channel_unentitled | no paid slot and not the sandbox channel | buy a channel |
429 sandbox_allowance_exceeded | sandbox allowance used | wait until resets_at |
429 gateway_busy, rate_limited | back-pressure | wait Retry-After, retry |
429 whatsapp_rate_limited | WhatsApp throttled the number | back off, retry later |
504 send_unconfirmed | outcome unknown; treat as possibly delivered | do not resend blindly; see Recipes |