Send a message
Send text, media, interactive, location, contact, or native Flow content.
Message state
Mark messages as seen, react, edit, delete, archive, or show typing state.
Media
Inspect, upload, download, or persist media.
Presence and calls
Observe presence where enabled and control supported call actions.
Message content
Sendconversation and a content object containing exactly one message kind. Do not send
type or place message fields at the request root.
"content": { "text": "Hello" }. For media, put its URL or
base64 bytes and caption inside content.image, content.video, content.file,
or content.voice. Supply exactly one media source.
mentions, quotedMessage, and isForwarded remain alongside conversation and content.
Requests with missing content, multiple message kinds, or misplaced fields fail
validation. requestPhoneNumber requires the contact’s conversation.id.
Interactive message request rules
Use one ofcontent.product, content.productList, content.order,
content.list, content.buttons, content.addressMessage, or content.flow.
Products and product lists use businessOwnerId; orders use sellerId.
These are public identity IDs for a business with a known phone number.
Catalog responses expose the owner’s public identity ID in belongsTo.
Product and collection IDs remain their original commerce identifiers.
For
content.addressMessage, requirements depend on the connected backend. Meta Cloud
API sessions require content.addressMessage.body and a two-letter uppercase
content.addressMessage.country code. Linked Device API sessions require
content.addressMessage.body and a nonblank content.addressMessage.buttonText call to action.
The method page is the request contract. Open its playground for parameters, schemas, responses, and raw cURL. Verified SDK tabs appear only when the corresponding client method exists.
Conversation identity
Send withconversation.id, conversation.phoneNumber, or conversation.bsuid.
At least one is required. If you supply several selectors, they must already
identify the same conversation. Supplying aliases does not link accounts.
IDs are opaque decimal strings scoped to the connected Number. Keep them as
strings. BSUIDs are scoped to the connected business portfolio. A Linked Device
connection requires an already known routable identity for a BSUID. Phone
numbers use E.164, including +. A username alone is not a send destination.
On a Meta Cloud API number, a conversation known only by its BSUID is sent to
that BSUID. When a phone number is also known for the conversation, the message
goes to the phone number. Reply to a user who messaged you without a visible
phone number by sending to the received conversation.id. WhatsApp does not
deliver one-tap, zero-tap, or copy-code authentication templates to a BSUID;
those sends fail with invalid_parameter.
Received messages identify their conversation with id and any available
phoneNumber, bsuid, or username. Received group messages include the author under
conversation.sender, with id and any available phoneNumber, bsuid, or username.
Send requests and send results omit sender. A hidden phone number is omitted;
a BSUID is never converted into a Linked Device user ID.
Message actions such as marking a message seen, reacting, and starring use id
for the message identifier and conversation for its destination:
Message identifiers
Message responses contain a Polymorfaid and a whatsapp_ids object with
exact provider references. linked_devices contains the Linked Device reference;
official_api contains the Official API reference. Unknown references are omitted.
Use id for message actions and quotedMessage.id for replies. Keep every ID as
a string. The Polymorfa ID does not encode test mode or connection type.
Acknowledgements return messages, an array of
{ id, whatsapp_ids, whatsapp_id? }. The singular field is a temporary alias
and is omitted when its provider reference is unavailable.
The earlier whatsapp_id field remains in native message responses and events
during the API version migration. Read whatsapp_ids for new integrations.
Polymorfa retains these message and routing identifiers with Hosted Message
Storage disabled. This does not enable message-content storage.
Cloud requests return their result synchronously, including when you send
Prefer: respond-async. Linked-device requests that accept the preference
return 202 with Preference-Applied: respond-async; their result arrives in
command.result. Do not assume a preference was applied without that header.
When a message action fails, its docs URL points to the matching failure
section on that method’s reference page. result_unknown means the command’s
result was not received; it does not prove the action failed. Check resulting
events before retrying. A dispatch failure returns command_dispatch_failed
with status 503.
Retry sends safely
Send anIdempotency-Key header with a message write to make it safe to
retry. Use a new random value, such as a UUID, for each message or action, and
send the same value when you retry it. The key is 1 to 255 bytes of UTF-8.
These methods accept the header:
Other messaging methods ignore the header.
Keys belong to the credential and API version that sent them. Retry with the
same credential and
Polymorfa-Version: the same key sent with another API
key, project token, client token, or API version is a separate request.
Polymorfa records successful results and server errors. A server error such as
result_unknown or command_dispatch_failed means the message may have been
sent, so a retry with the same key returns that error again instead of sending
a second copy. Check message events for the outcome, and use a new key only
when you have confirmed that the message was not sent. A 4xx error,
session_not_ready, or credential_verification_unavailable means nothing
ran; retry with the same key after you fix the cause. A server error from
the catalog check on a Graph product or product-list send also means nothing
was sent; a retry with the same key runs the send again.
Polymorfa keeps the key, a hash of the request, and the response status and
error code for 24 hours. It does not keep message content, message IDs,
recipients, or the response body, so a retry after a success can’t return the
original response. Keep the first response when you receive it, and use
message events or webhooks to find a message whose response was lost.
If the first request started but its outcome wasn’t recorded, the message might
have been sent. The key then returns idempotency_outcome_unknown for 24 hours
instead of sending again. Check message events, and use a new key only after you
confirm that the message was not sent.
In the TypeScript SDK, pass the key in the request options:
messages.send(session, body, { idempotencyKey }). The SDK retries a request
that carries a key after a network failure or a retryable status, using the
same key. If the first attempt succeeded but its response was lost, the retry
fails with idempotency_completed. See SDKs.