Skip to content
Linq Copy agent prompt

Event Log

Server-sent event streams — the same events the webhooks carry, held open instead of delivered.

List account events
GET/v1/event_log
ModelsExpand Collapse
AccountEvent object { id, data, timestamp, type }

One canonical account event envelope. Account sequence is transport metadata and is not a member.

id: string
data: object { message }

Extensible event-specific data.

message: optional Message { id, chat_id, created_at, 8 more }

Frozen message snapshot on newly materialized incoming-message events. On read-back surfaces — the transcript, event log, event stream and replays — zero-day messages carry empty parts and zero_retention: true. Webhook deliveries and the send response, including an idempotent retry, carry the content. Scrubbed ephemeral snapshots are omitted with their event. Older retained events may omit the snapshot; use the chat transcript for history.

id: string

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.

chat_id: string

The chat this message belongs to.

created_at: string
formatdate-time
direction: "inbound" or "outbound"

inbound (from the customer) or outbound (sent by you).

One of the following:
"inbound"
"outbound"
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 more

The message content — sendable parts, plus the inbound-only types on inbound messages. Empty only when zero_retention is true.

One of the following:
Text object { body, type, preview_url }

A plain text message.

body: string

The message text. URLs render as tappable links.

maxLength4096
type: "text"
preview_url: optional boolean

Render a preview card for the first URL in body. Defaults to false.

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.

kind: "image" or "video" or "audio" or 2 more

Which kind of media this is. Captions apply to image, video and document only; filename applies to document only.

One of the following:
"image"
"video"
"audio"
"document"
"sticker"
type: "media"
byte_size: optional number

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: optional string

Caption rendered with the media. Image, video and document only — a caption on an audio or sticker part is rejected, never dropped.

maxLength1024
filename: optional string

Display filename. Documents only — a filename on any other kind is rejected, never dropped.

maxLength240
media_id: optional string

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.

maxLength256
mime_type: optional string

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.

source_url: optional string

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.

maxLength2048
upload_ref: optional string

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.

maxLength256
url: optional string

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.

maxLength2048
voice: optional boolean

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.

language: string

The template’s language-and-locale code, e.g. en_US.

maxLength32
name: string

The approved template’s name. Lowercase letters, digits and underscores only.

maxLength512
type: "template"
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.

index: number
payload: string
maxLength256
type: "quick_reply" or "url"
One of the following:
"quick_reply"
"url"
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.

card_index: number
kind: "image" or "video"
One of the following:
"image"
"video"
upload_ref: string

A durable handle returned by POST /v1/media. It is resolved within this key’s account and partition, then consumed into a transient channel media identifier only at delivery.

maxLength256
buttons: optional array of object { index, payload, type }
index: number
payload: string
maxLength256
type: "quick_reply" or "url"
One of the following:
"quick_reply"
"url"
parameters: optional unknown

Named values for this card’s body placeholders.

header: optional object { kind, upload_ref }

The uploaded media used for this approved template’s media header. Omit unless this template’s send_schema requires it.

kind: "image" or "video" or "document"
One of the following:
"image"
"video"
"document"
upload_ref: string

A durable handle returned by POST /v1/media.

maxLength256
parameters: optional unknown

Named values for the template’s placeholders. The shape is per-template: each registered template publishes its own parameter schema — hand THAT schema to your model when composing a specific template. Omit for templates with no placeholders.

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.

body: string

The message body.

maxLength1024
button: string

Label of the button that opens the list.

maxLength20
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.

rows: array of object { id, title, description }

This section’s rows. The 10-row limit is on the TOTAL across all sections, so a second section reduces what this one may hold.

id: string

Your identifier for the row — echoed back as the interactive_reply’s id.

maxLength200
title: string

The row’s visible title.

maxLength24
description: optional string

Secondary text under the title.

maxLength72
title: optional string

Section title. Required when the list has more than one section.

maxLength24
type: "interactive_list"
header: optional string

Header text above the body.

maxLength60
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.

body: string

The message body.

maxLength1024
buttons: array of object { id, title }

The tappable buttons — at most three.

id: string

Your identifier for the button — echoed back as the interactive_reply’s id.

maxLength256
title: string

The button’s visible label.

maxLength20
type: "interactive_buttons"
header: optional string

Header text above the body.

maxLength60
media_header: optional object { kind, upload_ref }

Uploaded image, video, or document above the body. Use either header or media_header, never both.

kind: "image" or "video" or "document"
One of the following:
"image"
"video"
"document"
upload_ref: string

A durable handle returned by POST /v1/media.

maxLength256
InteractiveCtaURL object { body, display_text, type, 3 more }

An interactive call-to-action message: body text plus one button that opens a URL.

body: string

The message body.

maxLength1024
display_text: string

The button’s visible label.

maxLength20
type: "interactive_cta_url"
url: string

The URL the button opens. Must be absolute, with an http or https scheme.

maxLength2000
header: optional string

Header text above the body.

maxLength60
LocationRequest object { body, type }

Asks the customer to share a location. The response arrives as a location part.

body: string

Prompt shown above the send-location button.

maxLength1024
type: "location_request"
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.

body: string

Prompt shown above the address form.

maxLength1024
country: "IN"

ISO country code. Structured address requests are currently available only in India.

type: "address_request"
Contacts object { contacts, type }

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.

name: object { formatted_name, first_name, last_name, 3 more }
formatted_name: string

The contact’s full display name.

maxLength512
first_name: optional string
maxLength256
last_name: optional string
maxLength256
middle_name: optional string
maxLength256
prefix: optional string
maxLength256
suffix: optional string
maxLength256
addresses: optional array of object { city, country, country_code, 4 more }
city: optional string
maxLength256
country: optional string
maxLength256
country_code: optional string
maxLength64
kind: optional string

A label for the address, e.g. WORK, HOME.

maxLength64
state: optional string
maxLength256
street: optional string
maxLength256
zip: optional string
maxLength256
birthday: optional string

The contact’s birthday as YYYY-MM-DD.

emails: optional array of object { email, kind }
email: string
maxLength256
kind: optional string

A label for the address, e.g. WORK, HOME.

maxLength64
org: optional object { company, department, title }
company: optional string
maxLength256
department: optional string
maxLength256
title: optional string
maxLength256
phones: optional array of object { phone, kind, messaging_id }
phone: string

The phone number, ideally in +E.164 form.

maxLength256
kind: optional string

A label for the number, e.g. CELL, MAIN, WORK, HOME.

maxLength64
messaging_id: optional string

The contact’s messaging-account identifier on this channel, when known — makes the card openable in the messaging app.

maxLength256
urls: optional array of object { url, kind }
url: string
maxLength256
kind: optional string

A label for the URL, e.g. WORK, HOME.

maxLength64
type: "contacts"
Location object { latitude, longitude, type, 2 more }

A location pin.

latitude: number

Latitude in decimal degrees.

maximum90
minimum-90
longitude: number

Longitude in decimal degrees.

maximum180
minimum-180
type: "location"
address: optional string

The place’s address, shown under the name.

maxLength1000
name: optional string

The place’s name, shown on the pin.

maxLength1000
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.

emoji: string

A single emoji. Send an empty string to remove a previous reaction to the same message.

maxLength32
type: "reaction"
channel_message_id: optional string

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.

maxLength128
message_id: optional string

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.

maxLength256
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.

id: string

The id you assigned to the chosen row or button.

maxLength200
kind: "list_reply" or "button_reply"

Which interactive message kind was answered.

One of the following:
"list_reply"
"button_reply"
title: string

The chosen row’s or button’s visible title.

maxLength24
type: "interactive_reply"
description: optional string

The chosen list row’s secondary text, when it had one.

maxLength72
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.

payload: string

The quick_reply slot’s payload from your send, or the button label when the slot was omitted.

maxLength256
text: string

The tapped button’s label, as the customer saw it.

maxLength25
type: "template_reply"
AddressReply object { type, values, saved_address_id }

Inbound only: the structured values submitted in response to an address request.

type: "address_reply"
values: object { address, building_name, city, 8 more }
address: optional string
maxLength1024
building_name: optional string
maxLength1024
city: optional string
maxLength1024
floor_number: optional string
maxLength1024
house_number: optional string
maxLength1024
landmark_area: optional string
maxLength1024
name: optional string
maxLength1024
phone_number: optional string
maxLength64
pin_code: optional string
maxLength6
state: optional string
maxLength1024
tower_number: optional string
maxLength1024
saved_address_id: optional string

Identifier of the selected saved address, when one was selected.

maxLength256
Order object { catalog_id, items, type, text }

Inbound only — never sendable. An order the customer placed from a product catalog.

catalog_id: string

The catalog the ordered items belong to.

maxLength256
items: array of object { currency, price, product_retailer_id, quantity }

The ordered items.

currency: string

ISO 4217 currency code for price.

maxLength8
price: number

The per-item price at order time.

minimum0
product_retailer_id: string

Your identifier for the product, as registered in the catalog.

maxLength256
quantity: number
minimum0
type: "order"
text: optional string

Free text the customer attached to the order, when any.

maxLength4096
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.

type: "referral"
body: optional string

The ad’s body text at click time.

maxLength4096
click_id: optional string

The click identifier assigned by the ad platform, for attribution.

maxLength256
headline: optional string

The ad’s headline at click time.

maxLength4096
image_url: optional string

URL of the ad’s image creative, when media_kind is image.

maxLength2048
media_kind: optional string

The ad creative’s media kind, e.g. image or video.

maxLength64
source_id: optional string

The ad or post id.

maxLength256
source_kind: optional string

What the source was, e.g. ad or post.

maxLength64
source_url: optional string

The URL of the ad or post the customer came from.

maxLength2048
video_url: optional string

URL of the ad’s video creative, when media_kind is video.

maxLength2048
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.

body: string

Human-readable description of the system event.

maxLength4096
type: "system"
event: optional string

The system event’s kind, when the channel identifies one. New kinds appear over time — treat unknown values as informational.

maxLength64
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.

raw: unknown

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.

type: "unsupported"
kind: optional string

The channel’s name for the message kind, when it declares one.

maxLength64
status: string

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.

channel_message_id: optional string

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.

code: string

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.

message: string

Human-readable, written for a person reading it. Do not branch on it.

from: optional string

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.

reply_to_message_id: optional string

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.

zero_retention: optional true

True when authored content is deliberately absent under zero-day retention. Retention applies only within Linq’s systems. The WhatsApp channel operator’s own processing and retention of message content are governed by its terms and are not affected by this setting.

timestamp: string
formatdate-time
type: string
AccountEventPage object { data, has_more, next_cursor }
data: array of AccountEvent { id, data, timestamp, type }
id: string
data: object { message }

Extensible event-specific data.

message: optional Message { id, chat_id, created_at, 8 more }

Frozen message snapshot on newly materialized incoming-message events. On read-back surfaces — the transcript, event log, event stream and replays — zero-day messages carry empty parts and zero_retention: true. Webhook deliveries and the send response, including an idempotent retry, carry the content. Scrubbed ephemeral snapshots are omitted with their event. Older retained events may omit the snapshot; use the chat transcript for history.

id: string

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.

chat_id: string

The chat this message belongs to.

created_at: string
formatdate-time
direction: "inbound" or "outbound"

inbound (from the customer) or outbound (sent by you).

One of the following:
"inbound"
"outbound"
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 more

The message content — sendable parts, plus the inbound-only types on inbound messages. Empty only when zero_retention is true.

One of the following:
Text object { body, type, preview_url }

A plain text message.

body: string

The message text. URLs render as tappable links.

maxLength4096
type: "text"
preview_url: optional boolean

Render a preview card for the first URL in body. Defaults to false.

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.

kind: "image" or "video" or "audio" or 2 more

Which kind of media this is. Captions apply to image, video and document only; filename applies to document only.

One of the following:
"image"
"video"
"audio"
"document"
"sticker"
type: "media"
byte_size: optional number

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: optional string

Caption rendered with the media. Image, video and document only — a caption on an audio or sticker part is rejected, never dropped.

maxLength1024
filename: optional string

Display filename. Documents only — a filename on any other kind is rejected, never dropped.

maxLength240
media_id: optional string

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.

maxLength256
mime_type: optional string

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.

source_url: optional string

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.

maxLength2048
upload_ref: optional string

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.

maxLength256
url: optional string

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.

maxLength2048
voice: optional boolean

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.

language: string

The template’s language-and-locale code, e.g. en_US.

maxLength32
name: string

The approved template’s name. Lowercase letters, digits and underscores only.

maxLength512
type: "template"
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.

index: number
payload: string
maxLength256
type: "quick_reply" or "url"
One of the following:
"quick_reply"
"url"
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.

card_index: number
kind: "image" or "video"
One of the following:
"image"
"video"
upload_ref: string

A durable handle returned by POST /v1/media. It is resolved within this key’s account and partition, then consumed into a transient channel media identifier only at delivery.

maxLength256
buttons: optional array of object { index, payload, type }
index: number
payload: string
maxLength256
type: "quick_reply" or "url"
One of the following:
"quick_reply"
"url"
parameters: optional unknown

Named values for this card’s body placeholders.

header: optional object { kind, upload_ref }

The uploaded media used for this approved template’s media header. Omit unless this template’s send_schema requires it.

kind: "image" or "video" or "document"
One of the following:
"image"
"video"
"document"
upload_ref: string

A durable handle returned by POST /v1/media.

maxLength256
parameters: optional unknown

Named values for the template’s placeholders. The shape is per-template: each registered template publishes its own parameter schema — hand THAT schema to your model when composing a specific template. Omit for templates with no placeholders.

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.

body: string

The message body.

maxLength1024
button: string

Label of the button that opens the list.

maxLength20
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.

rows: array of object { id, title, description }

This section’s rows. The 10-row limit is on the TOTAL across all sections, so a second section reduces what this one may hold.

id: string

Your identifier for the row — echoed back as the interactive_reply’s id.

maxLength200
title: string

The row’s visible title.

maxLength24
description: optional string

Secondary text under the title.

maxLength72
title: optional string

Section title. Required when the list has more than one section.

maxLength24
type: "interactive_list"
header: optional string

Header text above the body.

maxLength60
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.

body: string

The message body.

maxLength1024
buttons: array of object { id, title }

The tappable buttons — at most three.

id: string

Your identifier for the button — echoed back as the interactive_reply’s id.

maxLength256
title: string

The button’s visible label.

maxLength20
type: "interactive_buttons"
header: optional string

Header text above the body.

maxLength60
media_header: optional object { kind, upload_ref }

Uploaded image, video, or document above the body. Use either header or media_header, never both.

kind: "image" or "video" or "document"
One of the following:
"image"
"video"
"document"
upload_ref: string

A durable handle returned by POST /v1/media.

maxLength256
InteractiveCtaURL object { body, display_text, type, 3 more }

An interactive call-to-action message: body text plus one button that opens a URL.

body: string

The message body.

maxLength1024
display_text: string

The button’s visible label.

maxLength20
type: "interactive_cta_url"
url: string

The URL the button opens. Must be absolute, with an http or https scheme.

maxLength2000
header: optional string

Header text above the body.

maxLength60
LocationRequest object { body, type }

Asks the customer to share a location. The response arrives as a location part.

body: string

Prompt shown above the send-location button.

maxLength1024
type: "location_request"
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.

body: string

Prompt shown above the address form.

maxLength1024
country: "IN"

ISO country code. Structured address requests are currently available only in India.

type: "address_request"
Contacts object { contacts, type }

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.

name: object { formatted_name, first_name, last_name, 3 more }
formatted_name: string

The contact’s full display name.

maxLength512
first_name: optional string
maxLength256
last_name: optional string
maxLength256
middle_name: optional string
maxLength256
prefix: optional string
maxLength256
suffix: optional string
maxLength256
addresses: optional array of object { city, country, country_code, 4 more }
city: optional string
maxLength256
country: optional string
maxLength256
country_code: optional string
maxLength64
kind: optional string

A label for the address, e.g. WORK, HOME.

maxLength64
state: optional string
maxLength256
street: optional string
maxLength256
zip: optional string
maxLength256
birthday: optional string

The contact’s birthday as YYYY-MM-DD.

emails: optional array of object { email, kind }
email: string
maxLength256
kind: optional string

A label for the address, e.g. WORK, HOME.

maxLength64
org: optional object { company, department, title }
company: optional string
maxLength256
department: optional string
maxLength256
title: optional string
maxLength256
phones: optional array of object { phone, kind, messaging_id }
phone: string

The phone number, ideally in +E.164 form.

maxLength256
kind: optional string

A label for the number, e.g. CELL, MAIN, WORK, HOME.

maxLength64
messaging_id: optional string

The contact’s messaging-account identifier on this channel, when known — makes the card openable in the messaging app.

maxLength256
urls: optional array of object { url, kind }
url: string
maxLength256
kind: optional string

A label for the URL, e.g. WORK, HOME.

maxLength64
type: "contacts"
Location object { latitude, longitude, type, 2 more }

A location pin.

latitude: number

Latitude in decimal degrees.

maximum90
minimum-90
longitude: number

Longitude in decimal degrees.

maximum180
minimum-180
type: "location"
address: optional string

The place’s address, shown under the name.

maxLength1000
name: optional string

The place’s name, shown on the pin.

maxLength1000
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.

emoji: string

A single emoji. Send an empty string to remove a previous reaction to the same message.

maxLength32
type: "reaction"
channel_message_id: optional string

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.

maxLength128
message_id: optional string

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.

maxLength256
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.

id: string

The id you assigned to the chosen row or button.

maxLength200
kind: "list_reply" or "button_reply"

Which interactive message kind was answered.

One of the following:
"list_reply"
"button_reply"
title: string

The chosen row’s or button’s visible title.

maxLength24
type: "interactive_reply"
description: optional string

The chosen list row’s secondary text, when it had one.

maxLength72
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.

payload: string

The quick_reply slot’s payload from your send, or the button label when the slot was omitted.

maxLength256
text: string

The tapped button’s label, as the customer saw it.

maxLength25
type: "template_reply"
AddressReply object { type, values, saved_address_id }

Inbound only: the structured values submitted in response to an address request.

type: "address_reply"
values: object { address, building_name, city, 8 more }
address: optional string
maxLength1024
building_name: optional string
maxLength1024
city: optional string
maxLength1024
floor_number: optional string
maxLength1024
house_number: optional string
maxLength1024
landmark_area: optional string
maxLength1024
name: optional string
maxLength1024
phone_number: optional string
maxLength64
pin_code: optional string
maxLength6
state: optional string
maxLength1024
tower_number: optional string
maxLength1024
saved_address_id: optional string

Identifier of the selected saved address, when one was selected.

maxLength256
Order object { catalog_id, items, type, text }

Inbound only — never sendable. An order the customer placed from a product catalog.

catalog_id: string

The catalog the ordered items belong to.

maxLength256
items: array of object { currency, price, product_retailer_id, quantity }

The ordered items.

currency: string

ISO 4217 currency code for price.

maxLength8
price: number

The per-item price at order time.

minimum0
product_retailer_id: string

Your identifier for the product, as registered in the catalog.

maxLength256
quantity: number
minimum0
type: "order"
text: optional string

Free text the customer attached to the order, when any.

maxLength4096
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.

type: "referral"
body: optional string

The ad’s body text at click time.

maxLength4096
click_id: optional string

The click identifier assigned by the ad platform, for attribution.

maxLength256
headline: optional string

The ad’s headline at click time.

maxLength4096
image_url: optional string

URL of the ad’s image creative, when media_kind is image.

maxLength2048
media_kind: optional string

The ad creative’s media kind, e.g. image or video.

maxLength64
source_id: optional string

The ad or post id.

maxLength256
source_kind: optional string

What the source was, e.g. ad or post.

maxLength64
source_url: optional string

The URL of the ad or post the customer came from.

maxLength2048
video_url: optional string

URL of the ad’s video creative, when media_kind is video.

maxLength2048
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.

body: string

Human-readable description of the system event.

maxLength4096
type: "system"
event: optional string

The system event’s kind, when the channel identifies one. New kinds appear over time — treat unknown values as informational.

maxLength64
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.

raw: unknown

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.

type: "unsupported"
kind: optional string

The channel’s name for the message kind, when it declares one.

maxLength64
status: string

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.

channel_message_id: optional string

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.

code: string

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.

message: string

Human-readable, written for a person reading it. Do not branch on it.

from: optional string

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.

reply_to_message_id: optional string

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.

zero_retention: optional true

True when authored content is deliberately absent under zero-day retention. Retention applies only within Linq’s systems. The WhatsApp channel operator’s own processing and retention of message content are governed by its terms and are not affected by this setting.

timestamp: string
formatdate-time
type: string
has_more: boolean

True when the log continues past next_cursor. Read this, not the length of data: a page can be empty while the log continues.

next_cursor: string

Exclusive opaque cursor for the next page: the position this page read through, which is at or past the last event returned. It is at least the last event’s position, and past it when the page reads through events not delivered to this key (numbers in an environment) or events omitted because an ephemeral message expired. It is present on an empty page too when has_more is true — pass it back to continue — and null only when nothing lies past the position asked for.