Ir al contenido
Start free

Sending messages

Esta página aún no está disponible en tu idioma.

Call wabery.messages.send() or POST /v1/messages. Pass a channel, target a conversation or opted-in WhatsApp number, and include exactly one content field.

Wabery accepts at most one new individual message every 6 seconds for the same channel sender and recipient. The limit applies independently to WhatsApp, Instagram, and Messenger. Different recipients, senders, or channel platforms have separate limits.

If a second POST /v1/messages request arrives during that window, Wabery does not create or queue the message. It returns 429:

{
"error": "message_pair_rate_limit",
"message": "Messaging to this recipient is rate limited. Retry after the indicated delay.",
"channel_id": "channel_...",
"channel_platform": "MESSENGER",
"queued": false,
"retryable": true,
"retry_after": 4
}

Honor the Retry-After response header before retrying. The response also sets RateLimit-Limit: 1, RateLimit-Remaining: 0, RateLimit-Reset to the Unix reset timestamp, and X-Wabery-Limit-Type: message_pair_rate_limit.

Use one stable Idempotency-Key for every attempt at the same logical send. If Wabery already has that key, it replays the existing result before applying a new pair-limit check. A different request body with the same key still returns 409.

Wabery also applies pair pacing at the final provider boundary to messages from the inbox, automations, and workers. Those internal sends can remain queued until their safe delivery slot. WhatsApp group sends are excluded.

If the distributed delivery limiter is unavailable, the API fails closed: it returns 503 message_rate_limit_unavailable, Retry-After: 1, and does not queue the message. See Errors and rate limits for retry guidance.

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.

Commerce is available for individual WhatsApp recipients only. Use the flattened Wabery shapes; the provider adapter builds Meta’s action envelope:

await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
interactive: {
type: "product_list",
header: { type: "text", text: "Summer collection" },
body: { text: "Choose an item" },
catalog_id: "catalog_...",
sections: [{
title: "Featured",
product_items: [{ product_retailer_id: "sku_123" }],
}],
},
});

product sends one SKU, product_list allows up to 10 sections and 30 products total, and catalog_message opens the catalog. Groups reject all three. Inbound individual messages preserve order, referral, ctwa_clid, referred_product, and identity_key_hash in message history and message.received webhooks.

Template parameters support text, image, document, video, currency, date_time, coupon_code, payload, group_id, and Flow action values.

WhatsApp supports two call-related interactive messages through wabery.messages.send():

  • call_permission_request asks the user to authorize future business-initiated calls;
  • voice_call displays a button that lets the user call the business.

Neither message carries audio, and voice_call does not place an outbound call. The business must use the Calling API control flow after permission is granted. See WhatsApp Calling for the complete SDK, REST, MCP, WebRTC/SIP, and webhook sequence.

Meta’s WhatsApp Payments API is available only for eligible accounts and supported countries. Wabery supports the regional order_details and order_status interactive types without rewriting their country-specific payment objects.

For India, provide Meta’s complete review_and_pay action:

await wabery.messages.send({
channelId: "channel_...",
to: "+919876543210",
idempotencyKey: "india-order-123",
interactive: {
type: "order_details",
body: { text: "Review and pay for your order" },
action: {
...metaIndiaOrderAction,
name: "review_and_pay",
},
},
});

For Singapore, provide Meta’s nested order-details object instead of body or action:

await wabery.messages.send({
channelId: "channel_...",
to: "+6591234567",
idempotencyKey: "singapore-order-123",
interactive: {
type: "order_details",
order_details: metaSingaporeOrderDetails,
},
});

Send a regional order-status update with Meta’s complete review_order action:

await wabery.messages.send({
channelId: "channel_...",
to: "+919876543210",
idempotencyKey: "order-status-123-paid",
interactive: {
type: "order_status",
body: { text: "Your payment was received" },
action: {
...metaOrderStatusAction,
name: "review_order",
},
},
});

The placeholder objects above must contain the current fields required by Meta for the account’s country, payment configuration, currency, totals, order, and payment method. Wabery validates the outer regional shape and preserves the nested object. Meta remains responsible for eligibility and payment processing; your payment provider may charge separately. Groups reject payment interactive messages.

Use groupId instead of conversationId or to. Groups support text, media, approved text/media templates, and pin/unpin only:

await wabery.messages.send({
channelId: "channel_...",
groupId: "group_opaque_7f3a92",
idempotencyKey: "welcome-group-2026-08-22",
pin: { type: "pin", messageId: "wamid....", expirationDays: 7 },
});

The REST pin field is expiration_days. Authentication/interactive templates, commerce, reactions, locations, contacts, calls, edit/delete, ephemeral, and view-once messages are unsupported in groups. See WhatsApp Groups for management and webhooks.

Group sends reserve a durable intent before Wabery contacts Meta. Reuse the same idempotencyKey (or REST Idempotency-Key/idempotency_key) for retries. The SDK type, CLI, MCP tool, and REST API require a caller-stable key for groups. A normal successful response is 202 with object: "message"; if Meta acceptance or local persistence still needs reconciliation, Wabery returns 202 with object: "message_request" and status: "processing". Do not create a new key in response to that status, because it could send a duplicate group message. If Wabery still cannot determine provider acceptance after the reconciliation window, the same key returns 409 with whatsapp_group_send_acceptance_unknown; verify delivery in WhatsApp Manager before deciding whether to create a new send.

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.
groupId string Opaque WhatsApp group id; mutually exclusive with conversationId and to.
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 reply/button/prompt or individual-only product, product_list, and catalog_message.
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.
pin object Group-only pin/unpin; pin requires expirationDays (REST: expiration_days) from 1–30.
replyTo string WhatsApp message id to quote. A modifier — combines with any content field.
idempotencyKey string Required safe-retry key for groups. Generate it once and reuse it for retries (see Errors).