Skip to content
Start free

Sending messages

Send a message with wabery.messages.send() (or POST /v1/messages). Set the channelId returned by the API, target either an existing conversation or a dedicated-channel WhatsApp phone number with opt-in, and provide exactly one content field.

PHP and Python examples use the reusable clients from PHP, Python, and API clients.

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
text: "Thanks for your message",
});

Outside the 24-hour window, use an approved template instead of free-form text. Create and get one approved first — see WhatsApp templates — then send it by id, or by name plus language:

Templates can only be submitted and approved on dedicated WhatsApp channels whose Meta business account is verified and has a Meta payment method. The Wabery sandbox cannot submit your templates. Check channel.publish_readiness.can_submit_templates before creating templates; readiness failures return 412 with blockers such as BUSINESS_NOT_VERIFIED or NO_PAYMENT_METHOD. See why WhatsApp templates need a payment method for the Meta billing reason.

await wabery.messages.send({
channelId: "channel_...",
to: "+14155550100",
template: {
name: "order_shipped",
language: "en",
components: [
{
type: "body",
parameters: [{ type: "text", text: "AB-2291" }],
},
],
},
});

Trigger a WhatsApp Flow to collect structured data in-chat:

await wabery.flows.send("flow_...", {
channelId: "channel_...",
contactId: "contact_...",
bodyText: "Tell us about your project",
});

flows.send sends an interactive flow message, which Meta only allows inside the 24-hour window. To send by the contact’s language, use flows.sendByConfigKey("config_key", { … }) — see Localization.

Proactive flows (outside the 24-hour window)

Section titled “Proactive flows (outside the 24-hour window)”

To reach a contact proactively, send an approved flow-type template (a template with a FLOW button) and pass the flow token via a button action:

await wabery.messages.send({
channelId: "channel_...",
to: "+14155550100",
template: {
name: "daily_reminder",
language: "en",
components: [
{
type: "button",
sub_type: "flow",
index: "0",
parameters: [{ type: "action", action: { flow_token: "..." } }],
},
],
},
});

Send an attachment through the same message endpoint. Channel support differs: WhatsApp accepts image, document, audio, and video; Instagram accepts image, audio, and video; Messenger accepts all four. The example below is a WhatsApp document because caption and filename are WhatsApp-only.

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
media: {
type: "document",
link: "https://example.com/invoice.pdf",
filename: "invoice-2291.pdf",
caption: "Your invoice",
},
});

WhatsApp accepts exactly one of media.link or media.id. id is a WhatsApp-hosted media handle. Instagram and Messenger accept only a public HTTPS link that Meta can fetch without custom headers; both reject id, caption, and filename with HTTP 422. Instagram also rejects document attachments—send the document URL as an ordinary text message.

See Media messages for Instagram/Messenger examples, inbound payloads, download authentication and expiry, provider size limits, and the complete validation-error list.

type: "audio" sends a voice note or audio file — useful for replying in kind when a customer sends one.

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
media: { type: "audio", link: "https://example.com/reply.ogg" },
});

Set replyTo (reply_to) to the WhatsApp id of a message to quote it, so your message renders in a contextual bubble under it. It works with any content type — it is a modifier, not a content field, so it does not count toward the exactly-one-of rule.

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
replyTo: "wamid....",
text: "Yes, that one is still available",
});

When a customer replies to one of your messages, the message.received webhook carries messages[].replied_to with the quoted message_id and its from. Pass that message_id straight back as replyTo to keep a thread going. replied_to is null on a fresh message.

Share one or more contacts with contacts (1–20 cards), in the same shape Meta uses.

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
contacts: [
{
name: { formatted_name: "Ada Lovelace", first_name: "Ada" },
phones: [{ phone: "+15555550123", type: "WORK", wa_id: "15555550123" }],
org: { company: "Analytical Engines", title: "Lead" },
},
],
});

name.formatted_name is required, and Meta additionally requires at least one of first_name, last_name, middle_name, suffix or prefix — a card without one returns 400 naming the field rather than failing at Meta. Setting phones[].wa_id makes that number tappable as a WhatsApp contact in the card. Optional fields: birthday (YYYY-MM-DD), emails, urls, addresses, org.

Contacts a customer shares with you arrive as inbound messages of type contacts.

React to an existing WhatsApp message with reaction. Use an empty emoji string to remove a previous reaction.

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
reaction: {
messageId: "wamid....",
emoji: "👍",
},
});

Send a WhatsApp location pin with latitude and longitude. name and address are optional.

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
location: {
latitude: 37.7749,
longitude: -122.4194,
name: "San Francisco office",
},
});

Send a WhatsApp sticker by public HTTPS link or Meta media id:

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
sticker: {
link: "https://example.com/sticker.webp",
},
});

Provide exactly one of sticker.link or sticker.id.

Interactive reply buttons (up to 3), single-select lists, link buttons, and the location / contact info prompts are all sent with the interactive field. The SDK types match this wire shape exactly:

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
interactive: {
type: "button",
body: { text: "Confirm your order?" },
buttons: [
{ id: "confirm", title: "Confirm" },
{ id: "cancel", title: "Cancel" },
],
},
});

The full wire shape, with limits:

Terminal window
curl https://api.wabery.com/v1/messages \
-H "Authorization: Bearer $WABERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "channel_...",
"conversation_id": "conversation_...",
"interactive": {
"type": "button",
"body": { "text": "Confirm your order?" },
"buttons": [
{ "id": "confirm", "title": "Confirm" },
{ "id": "cancel", "title": "Cancel" }
]
}
}'

buttons: 1–3 entries; title ≤ 20 chars. Optional header ({ "type": "text", "text": "…" }, ≤ 60) and footer ({ "text": "…" }, ≤ 60).

A tap on a reply button or list row comes back as a message.received webhook carrying the id you set — deduplicate and branch on it.

The other three send nothing back to route on. A cta_url tap just opens the URL in the device browser and produces no webhook at all. A location request arrives as an ordinary inbound location message, and a contact info request as an inbound contacts message, so correlate those by conversation rather than by an id.

Field Type Notes
channelId string Required channel id from channels.list().
to string E.164 phone for dedicated WhatsApp channels (requires opt-in).
conversationId string Existing conversation id.
text string Free-form message, ≤ 4096 chars (within the window).
template object Approved WhatsApp template id, or name plus language.
media object Channel-supported image, document, video, or audio. WhatsApp accepts link/id; Instagram and Messenger require a public HTTPS link.
interactive object WhatsApp button, list, cta_url, location_request_message, or request_contact_info payload.
reaction object React to an existing WhatsApp message by messageId/message_id and emoji.
location object WhatsApp location pin with latitude, longitude, optional name and address.
sticker object WhatsApp sticker by public HTTPS link or Meta media id.
contacts array 1–20 contact cards. Each needs name.formatted_name plus one other name part.
replyTo string WhatsApp message id to quote. A modifier — combines with any content field.
idempotencyKey string Optional; safe-retry key (see Errors).