Send a message in a chat
Sends a reply on an existing chat’s unchanged sending number. The chat
supplies the customer and sender; this operation never creates, moves,
repins or fails over a conversation. Acceptance and refusal semantics are
otherwise identical to POST /v1/messages.
Body ParametersJSONExpand Collapse
parts: array of object { body, type, preview_url } or object { kind, type, byte_size, 8 more } or object { language, name, type, 4 more } or 8 moreThe message content — sendable parts only, at least one.
One outbound part per request. Each request has its own acceptance
result and optional idempotency key. Each accepted request maps to one
native channel message with its own identifier, delivery outcome and
charge evidence. A request carrying more than one
part is refused with HTTP 422 (multiple_parts_not_available, a 1xxx
error.code, 1019); the API neither fans it out nor offers an atomic
batch. Separate requests are independent, and no cross-request delivery
or receipt order is promised.
A customer known only by a username identifier can receive every message
type except a one-time-code template (a copy-code, one-tap or zero-tap
autofill button), which can be delivered only to a phone number. When
the template is known to carry such a button, the send is refused with
HTTP 422 recipient_phone_number_required (a 4xxx error.code, 4011),
pointered at the part, and nothing is accepted; deliver the code another
way.
The message content — sendable parts only, at least one.
One outbound part per request. Each request has its own acceptance
result and optional idempotency key. Each accepted request maps to one
native channel message with its own identifier, delivery outcome and
charge evidence. A request carrying more than one
part is refused with HTTP 422 (multiple_parts_not_available, a 1xxx
error.code, 1019); the API neither fans it out nor offers an atomic
batch. Separate requests are independent, and no cross-request delivery
or receipt order is promised.
A customer known only by a username identifier can receive every message
type except a one-time-code template (a copy-code, one-tap or zero-tap
autofill button), which can be delivered only to a phone number. When
the template is known to carry such a button, the send is refused with
HTTP 422 recipient_phone_number_required (a 4xxx error.code, 4011),
pointered at the part, and nothing is accepted; deliver the code another
way.
Media object { kind, type, byte_size, 8 more } A media message — image, video, audio, document, or sticker. The two identifiers are distinct namespaces: inbound media carries the channel’s transient media_id; outbound media carries our durable upload_ref from POST /v1/media.
A media message — image, video, audio, document, or sticker. The two identifiers are distinct namespaces: inbound media carries the channel’s transient media_id; outbound media carries our durable upload_ref from POST /v1/media.
kind: "image" or "video" or "audio" or 2 moreWhich kind of media this is. Captions apply to image, video and document only; filename applies to document only.
Which kind of media this is. Captions apply to image, video and document only; filename applies to document only.
Response only, inbound parts only: the exact size in bytes of the retained attachment, equal to the download’s Content-Length. Present only while the bytes are captured and retained — the same lifetime as a working url, ending 30 days after server receipt — so it may be absent from the webhook body when capture has not completed, and once present it never changes. Absent rather than zero. Never accepted on sends.
Caption rendered with the media. Image, video and document only — a caption on an audio or sticker part is rejected, never dropped.
Display filename. Documents only — a filename on any other kind is rejected, never dropped.
Inbound only: the channel’s transient media identifier. It expires after seven days and is never an outbound address; an outbound value is rejected with a pointer to media_id and upload_ref as the remedy.
Response only, inbound parts only: the media type the channel declared when the message arrived, verbatim. It may carry parameters (for example audio/ogg; codecs=opus) and may differ from the Content-Type the download answers with, which describes the retained bytes. Present on the webhook body and on every later read with the same value. Historical messages received before it was recorded omit this field. Never accepted on sends.
Outbound only: a public https URL of the bytes to send, as an alternative to upload_ref. Supply exactly one of upload_ref or source_url. The address is fetched once, before the send is accepted, and its exact bytes are stored as an ordinary upload in this account and test/live partition; the accepted message and every later read carry that upload’s upload_ref, never the address, and no retry fetches it again. The URL must be absolute https on the default port with no credentials and no fragment, must resolve to a public address (every redirect hop included, at most three), and must answer with the complete bytes and their exact media type within the fetch deadline. The bytes are then held to the same media type and size rules as POST /v1/media for the part’s kind; a mismatch is refused, never converted.
Outbound only: our durable handle returned by POST /v1/media. Handles visibly begin upload. and are reusable within the account and test/live partition that created them.
Response only: stable attachment download URL requiring the account Bearer key on every request. Pending capture returns 409 with Retry-After; retained bytes expire 30 days after server receipt. Historical unbound messages omit this field. Never accepted on sends.
Response only, inbound audio parts only: whether the channel reported the audio as a voice note, verbatim. true means it was recorded with the messaging client’s voice-recording feature; false means it was sent as an audio file. Present on the webhook body and on every later read with the same value. Absent means the channel did not say — never that the audio is not a voice note. Other kinds never carry it, and historical messages received before it was recorded omit it. Never accepted on sends.
Template object { language, name, type, 4 more } An approved message template — the one part type sendable outside an open customer service window. name and language identify the approved template; parameters fills its named placeholders. Authentication templates use this same generic part unchanged: the channel supplies the OTP substitution, so do not invent or pass a code parameter unless the template’s published parameter schema explicitly names one. Every sendable template is registered and approved ahead of the send and publishes its own parameter schema — a send referencing an unknown template, a missing or unknown parameter, or a value that breaks the template’s rules is rejected with a 422 naming the exact field.
An approved message template — the one part type sendable outside an open customer service window. name and language identify the approved template; parameters fills its named placeholders. Authentication templates use this same generic part unchanged: the channel supplies the OTP substitution, so do not invent or pass a code parameter unless the template’s published parameter schema explicitly names one. Every sendable template is registered and approved ahead of the send and publishes its own parameter schema — a send referencing an unknown template, a missing or unknown parameter, or a value that breaks the template’s rules is rejected with a 422 naming the exact field.
The approved template’s name. Lowercase letters, digits and underscores only.
buttons: optional array of object { index, payload, type } Send-time values for this approved template’s own dynamic buttons, in ascending index order: a url button whose destination ends in {{1}} takes the value appended there and is REQUIRED; a quick_reply button takes the payload returned to you when it is tapped and is OPTIONAL — omit it and the button returns its label. Fixed buttons have no slot. The approved template’s send_schema lists the (index, type) slots and an x-rule stating which are required; omit buttons entirely for a template with no url slot when the labels are what you want back.
Send-time values for this approved template’s own dynamic buttons, in ascending index order: a url button whose destination ends in {{1}} takes the value appended there and is REQUIRED; a quick_reply button takes the payload returned to you when it is tapped and is OPTIONAL — omit it and the button returns its label. Fixed buttons have no slot. The approved template’s send_schema lists the (index, type) slots and an x-rule stating which are required; omit buttons entirely for a template with no url slot when the labels are what you want back.
cards: optional array of object { card_index, kind, upload_ref, 2 more } Per-card values for an approved media-card carousel. The approved template’s send_schema provides structural bounds and an x-rule listing the exact card order, media kind, named body values, and dynamic button slots enforced by runtime L2 validation. Omit for non-carousel templates.
Per-card values for an approved media-card carousel. The approved template’s send_schema provides structural bounds and an x-rule listing the exact card order, media kind, named body values, and dynamic button slots enforced by runtime L2 validation. Omit for non-carousel templates.
InteractiveList object { body, button, sections, 3 more } An interactive list message: body text plus a button that opens a sectioned list of selectable rows. The customer’s selection arrives as an inbound interactive_reply part with kind list_reply, carrying the chosen row’s id.
An interactive list message: body text plus a button that opens a sectioned list of selectable rows. The customer’s selection arrives as an inbound interactive_reply part with kind list_reply, carrying the chosen row’s id.
sections: array of object { rows, title } The list’s sections, each holding selectable rows. At most 10 rows in total across ALL sections combined — not 10 per section.
The list’s sections, each holding selectable rows. At most 10 rows in total across ALL sections combined — not 10 per section.
InteractiveButtons object { body, buttons, type, 3 more } An interactive reply-buttons message: body text plus up to three tappable buttons. The customer’s tap arrives as an inbound interactive_reply part with kind button_reply, carrying the tapped button’s id.
An interactive reply-buttons message: body text plus up to three tappable buttons. The customer’s tap arrives as an inbound interactive_reply part with kind button_reply, carrying the tapped button’s id.
InteractiveCtaURL object { body, display_text, type, 3 more } An interactive call-to-action message: body text plus one button that opens a URL.
An interactive call-to-action message: body text plus one button that opens a URL.
LocationRequest object { body, type } Asks the customer to share a location. The response arrives as a location part.
Asks the customer to share a location. The response arrives as a location part.
AddressRequest object { body, country, type } Reserved for asking an eligible customer in India to submit a structured address. Sending this part is not available until business and customer eligibility can be verified at acceptance. Address responses already arrive as address_reply parts.
Reserved for asking an eligible customer in India to submit a structured address. Sending this part is not available until business and customer eligibility can be verified at acceptance. Address responses already arrive as address_reply parts.
Contacts object { contacts, type } One or more contact cards.
One or more contact cards.
contacts: array of object { name, addresses, birthday, 4 more } The contact cards to send. The channel permits far more; this API caps a message at five, because a message carrying hundreds of cards is a mistake rather than a use case.
The contact cards to send. The channel permits far more; this API caps a message at five, because a message carrying hundreds of cards is a mistake rather than a use case.
Reaction object { emoji, type, channel_message_id, message_id } An emoji reaction to an earlier message in the chat. message_id is this API’s Message.id for the target in both directions: it is what you send, and it is appended to an inbound reaction whenever the target is a message of the same chat. channel_message_id is the channel’s opaque identifier and appears on inbound reactions only.
An emoji reaction to an earlier message in the chat. message_id is this API’s Message.id for the target in both directions: it is what you send, and it is appended to an inbound reaction whenever the target is a message of the same chat. channel_message_id is the channel’s opaque identifier and appears on inbound reactions only.
A single emoji. Send an empty string to remove a previous reaction to the same message.
Inbound only: the channel’s opaque identifier for the message being reacted to. Always present on an inbound reaction, beside message_id whenever the target resolved. Match it literally to Message.channel_message_id in the chat transcript; never parse it or compare it with Message.id.
This API’s Message.id for the message being reacted to. Outbound: required, and the API resolves it into the channel namespace before sending, so the message must be one this account can see in the chat the send addresses, and — for a message this API sent — one the channel has already acknowledged. Inbound: present whenever the target is a message of the same chat, so you can keep only this identifier; absent when the chat does not hold the target.
ReturnsExpand Collapse
AcceptedMessage object { id, chat_id, created_at, 9 more } An accepted send: the message, plus how its sending number was chosen.
An accepted send: the message, plus how its sending number was chosen.
This API’s message id. Put this value in an outbound reaction’s message_id; an inbound reaction to this message carries the same value in its message_id, so this is the one identifier you need to keep. Mark-as-read targets the latest unread inbound message in a chat, so it takes the chat id.
parts: array of object { body, type, preview_url } or object { kind, type, byte_size, 8 more } or object { language, name, type, 4 more } or 15 moreThe message content — sendable parts, plus the inbound-only types on
inbound messages. Empty only when zero_retention is true.
The message content — sendable parts, plus the inbound-only types on
inbound messages. Empty only when zero_retention is true.
Media object { kind, type, byte_size, 8 more } A media message — image, video, audio, document, or sticker. The two identifiers are distinct namespaces: inbound media carries the channel’s transient media_id; outbound media carries our durable upload_ref from POST /v1/media.
A media message — image, video, audio, document, or sticker. The two identifiers are distinct namespaces: inbound media carries the channel’s transient media_id; outbound media carries our durable upload_ref from POST /v1/media.
kind: "image" or "video" or "audio" or 2 moreWhich kind of media this is. Captions apply to image, video and document only; filename applies to document only.
Which kind of media this is. Captions apply to image, video and document only; filename applies to document only.
Response only, inbound parts only: the exact size in bytes of the retained attachment, equal to the download’s Content-Length. Present only while the bytes are captured and retained — the same lifetime as a working url, ending 30 days after server receipt — so it may be absent from the webhook body when capture has not completed, and once present it never changes. Absent rather than zero. Never accepted on sends.
Caption rendered with the media. Image, video and document only — a caption on an audio or sticker part is rejected, never dropped.
Display filename. Documents only — a filename on any other kind is rejected, never dropped.
Inbound only: the channel’s transient media identifier. It expires after seven days and is never an outbound address; an outbound value is rejected with a pointer to media_id and upload_ref as the remedy.
Response only, inbound parts only: the media type the channel declared when the message arrived, verbatim. It may carry parameters (for example audio/ogg; codecs=opus) and may differ from the Content-Type the download answers with, which describes the retained bytes. Present on the webhook body and on every later read with the same value. Historical messages received before it was recorded omit this field. Never accepted on sends.
Outbound only: a public https URL of the bytes to send, as an alternative to upload_ref. Supply exactly one of upload_ref or source_url. The address is fetched once, before the send is accepted, and its exact bytes are stored as an ordinary upload in this account and test/live partition; the accepted message and every later read carry that upload’s upload_ref, never the address, and no retry fetches it again. The URL must be absolute https on the default port with no credentials and no fragment, must resolve to a public address (every redirect hop included, at most three), and must answer with the complete bytes and their exact media type within the fetch deadline. The bytes are then held to the same media type and size rules as POST /v1/media for the part’s kind; a mismatch is refused, never converted.
Outbound only: our durable handle returned by POST /v1/media. Handles visibly begin upload. and are reusable within the account and test/live partition that created them.
Response only: stable attachment download URL requiring the account Bearer key on every request. Pending capture returns 409 with Retry-After; retained bytes expire 30 days after server receipt. Historical unbound messages omit this field. Never accepted on sends.
Response only, inbound audio parts only: whether the channel reported the audio as a voice note, verbatim. true means it was recorded with the messaging client’s voice-recording feature; false means it was sent as an audio file. Present on the webhook body and on every later read with the same value. Absent means the channel did not say — never that the audio is not a voice note. Other kinds never carry it, and historical messages received before it was recorded omit it. Never accepted on sends.
Template object { language, name, type, 4 more } An approved message template — the one part type sendable outside an open customer service window. name and language identify the approved template; parameters fills its named placeholders. Authentication templates use this same generic part unchanged: the channel supplies the OTP substitution, so do not invent or pass a code parameter unless the template’s published parameter schema explicitly names one. Every sendable template is registered and approved ahead of the send and publishes its own parameter schema — a send referencing an unknown template, a missing or unknown parameter, or a value that breaks the template’s rules is rejected with a 422 naming the exact field.
An approved message template — the one part type sendable outside an open customer service window. name and language identify the approved template; parameters fills its named placeholders. Authentication templates use this same generic part unchanged: the channel supplies the OTP substitution, so do not invent or pass a code parameter unless the template’s published parameter schema explicitly names one. Every sendable template is registered and approved ahead of the send and publishes its own parameter schema — a send referencing an unknown template, a missing or unknown parameter, or a value that breaks the template’s rules is rejected with a 422 naming the exact field.
The approved template’s name. Lowercase letters, digits and underscores only.
buttons: optional array of object { index, payload, type } Send-time values for this approved template’s own dynamic buttons, in ascending index order: a url button whose destination ends in {{1}} takes the value appended there and is REQUIRED; a quick_reply button takes the payload returned to you when it is tapped and is OPTIONAL — omit it and the button returns its label. Fixed buttons have no slot. The approved template’s send_schema lists the (index, type) slots and an x-rule stating which are required; omit buttons entirely for a template with no url slot when the labels are what you want back.
Send-time values for this approved template’s own dynamic buttons, in ascending index order: a url button whose destination ends in {{1}} takes the value appended there and is REQUIRED; a quick_reply button takes the payload returned to you when it is tapped and is OPTIONAL — omit it and the button returns its label. Fixed buttons have no slot. The approved template’s send_schema lists the (index, type) slots and an x-rule stating which are required; omit buttons entirely for a template with no url slot when the labels are what you want back.
cards: optional array of object { card_index, kind, upload_ref, 2 more } Per-card values for an approved media-card carousel. The approved template’s send_schema provides structural bounds and an x-rule listing the exact card order, media kind, named body values, and dynamic button slots enforced by runtime L2 validation. Omit for non-carousel templates.
Per-card values for an approved media-card carousel. The approved template’s send_schema provides structural bounds and an x-rule listing the exact card order, media kind, named body values, and dynamic button slots enforced by runtime L2 validation. Omit for non-carousel templates.
InteractiveList object { body, button, sections, 3 more } An interactive list message: body text plus a button that opens a sectioned list of selectable rows. The customer’s selection arrives as an inbound interactive_reply part with kind list_reply, carrying the chosen row’s id.
An interactive list message: body text plus a button that opens a sectioned list of selectable rows. The customer’s selection arrives as an inbound interactive_reply part with kind list_reply, carrying the chosen row’s id.
sections: array of object { rows, title } The list’s sections, each holding selectable rows. At most 10 rows in total across ALL sections combined — not 10 per section.
The list’s sections, each holding selectable rows. At most 10 rows in total across ALL sections combined — not 10 per section.
InteractiveButtons object { body, buttons, type, 3 more } An interactive reply-buttons message: body text plus up to three tappable buttons. The customer’s tap arrives as an inbound interactive_reply part with kind button_reply, carrying the tapped button’s id.
An interactive reply-buttons message: body text plus up to three tappable buttons. The customer’s tap arrives as an inbound interactive_reply part with kind button_reply, carrying the tapped button’s id.
InteractiveCtaURL object { body, display_text, type, 3 more } An interactive call-to-action message: body text plus one button that opens a URL.
An interactive call-to-action message: body text plus one button that opens a URL.
LocationRequest object { body, type } Asks the customer to share a location. The response arrives as a location part.
Asks the customer to share a location. The response arrives as a location part.
AddressRequest object { body, country, type } Reserved for asking an eligible customer in India to submit a structured address. Sending this part is not available until business and customer eligibility can be verified at acceptance. Address responses already arrive as address_reply parts.
Reserved for asking an eligible customer in India to submit a structured address. Sending this part is not available until business and customer eligibility can be verified at acceptance. Address responses already arrive as address_reply parts.
Contacts object { contacts, type } One or more contact cards.
One or more contact cards.
contacts: array of object { name, addresses, birthday, 4 more } The contact cards to send. The channel permits far more; this API caps a message at five, because a message carrying hundreds of cards is a mistake rather than a use case.
The contact cards to send. The channel permits far more; this API caps a message at five, because a message carrying hundreds of cards is a mistake rather than a use case.
Reaction object { emoji, type, channel_message_id, message_id } An emoji reaction to an earlier message in the chat. message_id is this API’s Message.id for the target in both directions: it is what you send, and it is appended to an inbound reaction whenever the target is a message of the same chat. channel_message_id is the channel’s opaque identifier and appears on inbound reactions only.
An emoji reaction to an earlier message in the chat. message_id is this API’s Message.id for the target in both directions: it is what you send, and it is appended to an inbound reaction whenever the target is a message of the same chat. channel_message_id is the channel’s opaque identifier and appears on inbound reactions only.
A single emoji. Send an empty string to remove a previous reaction to the same message.
Inbound only: the channel’s opaque identifier for the message being reacted to. Always present on an inbound reaction, beside message_id whenever the target resolved. Match it literally to Message.channel_message_id in the chat transcript; never parse it or compare it with Message.id.
This API’s Message.id for the message being reacted to. Outbound: required, and the API resolves it into the channel namespace before sending, so the message must be one this account can see in the chat the send addresses, and — for a message this API sent — one the channel has already acknowledged. Inbound: present whenever the target is a message of the same chat, so you can keep only this identifier; absent when the chat does not hold the target.
InteractiveReply object { id, kind, title, 2 more } Inbound only — never sendable. The customer’s selection from an interactive_list (kind list_reply) or interactive_buttons (kind button_reply) message, carrying the id you assigned to the chosen row or button.
Inbound only — never sendable. The customer’s selection from an interactive_list (kind list_reply) or interactive_buttons (kind button_reply) message, carrying the id you assigned to the chosen row or button.
TemplateReply object { payload, text, type } Inbound only — never sendable. The customer tapped a quick-reply button on an approved template you sent. payload is the value you set on that button’s quick_reply slot when you sent the template, or the button’s label when you left the slot out; text is the label the customer saw. The message’s reply_to_message_id names the template message that carried the button. Distinct from interactive_reply, which answers an interactive_buttons or interactive_list message.
Inbound only — never sendable. The customer tapped a quick-reply button on an approved template you sent. payload is the value you set on that button’s quick_reply slot when you sent the template, or the button’s label when you left the slot out; text is the label the customer saw. The message’s reply_to_message_id names the template message that carried the button. Distinct from interactive_reply, which answers an interactive_buttons or interactive_list message.
AddressReply object { type, values, saved_address_id } Inbound only: the structured values submitted in response to an address request.
Inbound only: the structured values submitted in response to an address request.
Order object { catalog_id, items, type, text } Inbound only — never sendable. An order the customer placed from a product catalog.
Inbound only — never sendable. An order the customer placed from a product catalog.
Referral object { type, body, click_id, 7 more } Inbound only — never sendable. The ad or post context a customer’s first message arrived from (for example an ad whose call to action opens a chat). A referral also opens a free-entry-point customer window — see the chat’s customer_window.
Inbound only — never sendable. The ad or post context a customer’s first message arrived from (for example an ad whose call to action opens a chat). A referral also opens a free-entry-point customer window — see the chat’s customer_window.
System object { body, type, event } Inbound only — never sendable. A system event in the chat, such as the customer changing their number. New event kinds appear over time; body is always present and human-readable.
Inbound only — never sendable. A system event in the chat, such as the customer changing their number. New event kinds appear over time; body is always present and human-readable.
Unsupported object { raw, type, kind } Inbound only — never sendable. A message kind this API does not yet type natively, carried as a typed passthrough: raw holds the channel payload, so a new message kind is never a black box or a silent drop. Native part types for popular kinds are added over time; this part is the compatibility guarantee in the meantime.
Inbound only — never sendable. A message kind this API does not yet type natively, carried as a typed passthrough: raw holds the channel payload, so a new message kind is never a black box or a silent drop. Native part types for popular kinds are added over time; this part is the compatibility guarantee in the meantime.
The channel payload — any JSON value: object, array, string, number, boolean, or null. Deliberately unconstrained, because the whole point of this part is to carry a shape this API does not yet know. For rows written by API version 1.3.0 or later, object member order, number formatting and duplicated members survive storage and read-back. The canonical encoder may compact insignificant whitespace before storage. Rows written before API version 1.3.0 retain normalized JSON only: member order and number formatting may differ, and only the last duplicated member survives.
The message’s delivery state as last reported.
Inbound messages read received: they arrived, and no delivery of ours
ran. A message you sent starts accepted and moves through sent,
delivered and read as the channel reports them, with failed as the
definitive negative outcome.
unknown means exactly that: the send is INDETERMINATE and we decline
to guess. It is NOT terminal — an indeterminate send is never re-sent,
and it resolves to sent, delivered, read or failed when the
channel’s own report arrives — so keep observing rather than treating it
as an outcome.
States are reported asynchronously on the chat’s event sequence, and the set grows additively: treat a value you do not recognise as “no information” rather than failing on it.
Retention of this published status follows the chat transcript policy: there is no scheduled pruning, but no fixed minimum availability period is published yet. The present absence of a scheduled age-based sweep is not a promise of indefinite availability.
UNDER A sk_test_ KEY THESE ARE SIMULATED. A test-key send is reported
sent and then delivered within milliseconds, every time; delivered
there means a simulator accepted it, not that a device received it. Live
delivery can lag by hours, can never report delivered at all, and can
fail after the channel accepted the message — so do not calibrate
timeouts or “delivered means it arrived” logic against a test key.
The channel’s opaque identifier for this message, when the channel has assigned one. An inbound reaction’s channel_message_id matches this field literally; never parse it or compare it with id. It is absent while an accepted outbound message has not yet received a channel identifier, and until it is present an inbound reaction to this message carries only channel_message_id.
failure: optional object { code, message } Why a failed message failed, when we have a reason worth publishing.
Present only on status failed, and not on every one of those: not every
channel refusal has a reason in the published vocabulary, so a message
that failed on the wire may carry no failure at all. Its absence means
“no published reason”, never “no reason”.
status stays the thing to branch on. This is additive detail beside it,
so an integration written before this field existed still sees a terminal
failed and behaves exactly as it did.
Why a failed message failed, when we have a reason worth publishing.
Present only on status failed, and not on every one of those: not every
channel refusal has a reason in the published vocabulary, so a message
that failed on the wire may carry no failure at all. Its absence means
“no published reason”, never “no reason”.
status stays the thing to branch on. This is additive detail beside it,
so an integration written before this field existed still sees a terminal
failed and behaves exactly as it did.
The specific reason.
sender_deregistered — the number this message was going out from is no
longer a registered sending number, and the message never left. This is
terminal: it is not a pause and it does not clear, so retrying the same
message cannot succeed and that conversation is over. Reach this customer
by starting a new one on another of your numbers with a template, exactly
as you would any customer whose 24-hour window has closed; GET /v1/chats
shows which number each conversation uses. You are not billed for a
message that failed this way.
quality_hold_expired — this marketing message remained held by an
automatic safety control on its sending line for ten minutes and was not
sent. This is terminal: retrying the same message cannot succeed on that
conversation. Send marketing from another of your numbers with a template,
or contact support about the sending line. You are not billed for a message
that failed this way.
recipient_country_restricted — the business behind the sending number
is not currently permitted to message customers in this recipient’s
country, so the message was not delivered. This is terminal for the
message: sending it again cannot succeed while the restriction stands.
Most often this is because the business has not yet completed business
verification: complete it, and until then message customers in the
sending number’s own country. If the business is already verified, review
the messaging policy for the business’s category. It can arrive either as
an immediate failed or after the message was first reported sent;
both mean the same thing. You are not billed for a message that failed
this way.
payment_method_required — the business behind the sending number has
no working payment method on its messaging account, so this message was
not sent. This is terminal for the message: retrying it cannot succeed
until a payment method is added. Add or update the payment method in the
messaging account’s billing settings, then send again as a new message.
Replies inside an open 24-hour customer window are not affected and
continue to be delivered. It can arrive either as an immediate failed
or after the message was first reported sent; both mean the same thing.
You are not billed for a message that failed this way.
recipient_phone_number_required — this customer is known only by a
username identifier, not a phone number, and this message can be
delivered only to a phone number, so it was not sent. This is terminal
for the message: sending it again to this customer cannot succeed.
One-time-code templates (copy code, one-tap or zero-tap autofill) are the
usual cause; deliver the code another way. Other messages, including
templates without a one-time-code button, can still be sent to this
customer. When the template is known in advance to carry a one-time-code
button, the send is refused at once with the error
recipient_phone_number_required instead and nothing is accepted. It can
arrive either as an immediate failed or after the message was first
reported sent; both mean the same thing. You are not billed for a
message that failed this way.
The number this message left by, or arrived on, in E.164 (leading +).
Every message in one conversation carries the same value — a
conversation never moves to another number. Absent only if the number is
no longer one of yours.
from_selection: optional object { reason } Present only when this service chose the sending number for you — that
is, when you omitted from. A send that named from omits this rather
than reporting a choice nobody made.
Present only when this service chose the sending number for you — that
is, when you omitted from. A send that named from omits this rather
than reporting a choice nobody made.
Why this number was used. Exactly one of:
reused_active_chat— this customer already had a conversation and it continues on the same number.new_best_number— there was no existing conversation, so the best available number was chosen.
Like every other vocabulary here it is a plain string that may grow additively; treat a value you do not recognise as “chosen for you”.
This API’s id of the earlier message in this chat that this message
replies to. On an inbound message it is the message the customer quoted;
on an outbound message it is the reply_to_message_id you sent. Look the
value up in this chat’s transcript to show what was answered; it is the
same namespace as id, never a channel identifier.
Absent when there is no reply context this API can resolve — a message that quotes nothing, or one that quotes a message this API never recorded in this chat. Treat absence as “no reply context available”, not as proof the message was not a reply.
Send a message in a chat
curl https://whatsapp.messages.api.linqapp.com/v1/chats/$CHAT/messages \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $LINQ_WHATSAPP_API_KEY" \
-d '{
"parts": [
{
"body": "body",
"type": "text"
}
]
}'{
"id": "id",
"chat_id": "chat_id",
"created_at": "2019-12-27T18:11:19.117Z",
"direction": "inbound",
"parts": [
{
"body": "body",
"type": "text",
"preview_url": true
}
],
"status": "status",
"channel_message_id": "channel_message_id",
"failure": {
"code": "code",
"message": "message"
},
"from": "from",
"from_selection": {
"reason": "reason"
},
"reply_to_message_id": "reply_to_message_id",
"zero_retention": true
}Returns Examples
{
"id": "id",
"chat_id": "chat_id",
"created_at": "2019-12-27T18:11:19.117Z",
"direction": "inbound",
"parts": [
{
"body": "body",
"type": "text",
"preview_url": true
}
],
"status": "status",
"channel_message_id": "channel_message_id",
"failure": {
"code": "code",
"message": "message"
},
"from": "from",
"from_selection": {
"reason": "reason"
},
"reply_to_message_id": "reply_to_message_id",
"zero_retention": true
}