<Intro>

<EndpointHeader />

<Description>

Sends one WhatsApp message to one recipient. The request carries exactly one
kind of content: a message template, or free-form `text`, `image`, `video`,
`audio`, `sticker`, `document` or `location`. A request carrying none is
rejected with a `422`, and one carrying more than one is too.

A **template** is the only content WhatsApp delivers outside an open
customer service window, so it is what starts a conversation. Name the
template, optionally pick its language variant, and fill its placeholders in
`components`. A Bird-managed template selects its sender number from its
category, so the request carries no `from`; a template your workspace
authored requires one. Browse your workspace's templates in the Bird
dashboard.

**Free-form content** is deliverable only inside an open 24-hour customer
service window, which the contact opens by messaging or calling you and
resets each time they do it again. We do not track the window, so a send
outside one is accepted and then fails, carrying `service_window_expired` on
the message's `last_error`. Every free-form send requires `from`.

The `202` response is the accepted message, echoing the resolved content; it
is not a delivery confirmation. Follow delivery with
[Get a WhatsApp message](/docs/api/reference/get-whatsapp-message), the
per-message timeline from
[List events for a WhatsApp message](/docs/api/reference/list-whatsapp-message-events),
or `whatsapp.*` webhook events.

A template slug or language the catalogue does not stock, parameter values
that do not match the template's declared placeholders, a `from` this
workspace cannot send from, and a recipient that is neither a valid phone
number nor a business-scoped user ID each return a `422`.

</Description>

</Intro>

<Payload kind="request">

<Field name="to" type="string" required>

<Description>

The message recipient: a phone number in E.164 format (for example `+31612345678`), or the recipient's business-scoped user ID (for example `US.13491208655302741918`), which addresses a WhatsApp user whose phone number you do not have. A value that is neither returns a `422` `WhatsAppInvalidRecipient`. One-time-passcode templates require a phone number and return a `422` `WhatsAppRecipientNotSupportedForTemplate` when sent to a business-scoped user ID.

</Description>

</Field>

<Field name="from" type="string">

<Description>

The business phone number to send from, in E.164 format. Omit it for a Bird-managed template, which selects its own number from its category: setting it there returns a `422` `WhatsAppSenderNotAllowed`. Every other send, whether free-form content of any kind or a template your workspace authored, requires it, and the number must be one this workspace owns. Omitting it returns a `422` `WhatsAppSenderRequired`; naming a number this workspace cannot send from returns a `422` `WhatsAppSenderNotFound`; naming a number this workspace owns but that sits on a different WhatsApp Business Account than an authored template returns a `422` `WhatsAppSenderWABAMismatch`.

</Description>

</Field>

<Field name="template" type="object">

<Description>

The template to send. A Bird-managed template selects the sender number from the template's category, so `from` must be omitted. A template is the only content deliverable outside a customer service window.

</Description>

<FieldChildren kind="request">

<Field name="id" type="string" prefix="template." required>

<Description>

The template to send, by its id.

</Description>

</Field>

<Field name="slug" type="string" prefix="template." required>

<Description>

The template to send, by its slug handle (for example `bird_otp`).

</Description>

</Field>

<Field name="language" type="string" prefix="template.">

<Description>

Which of the template's languages to send, as a BCP-47 tag (for example `en` or `pt-BR`); Meta's underscore form (`pt_BR`) is accepted and normalized. Omit it to send the template's default language, unless the template sets `language_source_required`, in which case a send naming no language is rejected. When the template does not carry the language you ask for, its own `on_missing_language` setting decides whether the closest available language is sent instead or the send is rejected. The accepted message echoes the canonical BCP-47 form of the language it resolved to.

</Description>

</Field>

<Field name="components" type="array of object" prefix="template.">

<Description>

The values that fill the template's placeholders: one entry per content block that has placeholders, each carrying its `parameters`. A positional template takes its parameters in `{{n}}` order; a template with named parameters requires each parameter's `name` to match one the template declares. Either way, sending parameters that do not match what the template declares returns a `422` `WhatsAppTemplateParameterMismatch`.

</Description>

<FieldChildren kind="request">

<Field name="type" type="string" prefix="template.components." required>

<Description>

Which part of the template this fills in: `body` for the main text, `button` for a button's variable, `header` for the header's text, media or location, `carousel` for the cards.

</Description>

<Description>

Possible values (may grow over time): `header`, `body`, `button`, `carousel`

</Description>

</Field>

<Field name="parameters" type="array of object" prefix="template.components.">

<Description>

The values that fill this part's placeholders. A positional template takes them in `{{n}}` placeholder order; a template with named parameters requires each parameter's `name` to match one the template declares, and order then carries no meaning. Send it on every part except `carousel`, which carries its values on `cards`.

</Description>

<FieldChildren kind="request">

<Field name="type" type="string" prefix="template.components.parameters." required>

<Description>

The kind of value this parameter carries, which decides which of the fields below to send.

</Description>

</Field>

<Field name="text" type="string" prefix="template.components.parameters.">

<Description>

The value substituted into the placeholder, as a plain string. Send it on a `text` parameter.

</Description>

</Field>

<Field name="url" type="string" prefix="template.components.parameters.">

<Description>

Public `https` URL of the file a media header shows. Send it on an `image`, `video`, `gif` or `document` parameter. WhatsApp fetches it at send time, so it must still be reachable then, the same way a free-form media message's `url` must.

</Description>

</Field>

<Field name="location" type="object" prefix="template.components.parameters.">

<Description>

The point on the map a location header opens. Send it on a `location` parameter.

</Description>

<FieldChildren kind="request">

<Field name="latitude" type="number" prefix="template.components.parameters.location." required>

<Description>

Latitude in decimal degrees.

</Description>

</Field>

<Field name="longitude" type="number" prefix="template.components.parameters.location." required>

<Description>

Longitude in decimal degrees.

</Description>

</Field>

<Field name="name" type="string" prefix="template.components.parameters.location.">

<Description>

Name of the place, shown above the address.

</Description>

</Field>

<Field name="address" type="string" prefix="template.components.parameters.location.">

<Description>

Street address of the place. Shown only when `name` is also set.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="name" type="string" prefix="template.components.parameters.">

<Description>

Required when the template declares named parameters: the placeholder this value fills (for example `first_name`), matching exactly one of the names the template declares. Name every parameter in that case; order does not matter once names are supplied. Omit this field for a positional template, which takes its values in `{{n}}` order instead. Sending the wrong set of names, or leaving one out that the template requires, returns a `422` `WhatsAppTemplateParameterMismatch`.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="cards" type="array of object" prefix="template.components.">

<Description>

The values that fill each card of a carousel. Send it only on a `carousel` part. A carousel sends exactly the number of cards its template was approved with, so every card needs an entry.

</Description>

<FieldChildren kind="request">

<Field name="components" type="array of object" prefix="template.components.cards." required>

<Description>

The values that fill this card's blocks.

</Description>

<FieldChildren kind="request">

<Field name="type" type="string" prefix="template.components.cards.components." required>

<Description>

Which part of the card this fills in: `header` for the card's image or video, `body` for its text, `button` for a button's variable.

</Description>

<Description>

Possible values (may grow over time): `header`, `body`, `button`

</Description>

</Field>

<Field name="parameters" type="array of object" prefix="template.components.cards.components.">

<Description>

The values that fill this part's placeholders, in placeholder order.

</Description>

<FieldChildren kind="request">

<Field name="type" type="string" prefix="template.components.cards.components.parameters." required>

<Description>

The kind of value this parameter carries, which decides which of the fields below to send.

</Description>

</Field>

<Field name="text" type="string" prefix="template.components.cards.components.parameters.">

<Description>

The value substituted into the placeholder, as a plain string. Send it on a `text` parameter.

</Description>

</Field>

<Field name="url" type="string" prefix="template.components.cards.components.parameters.">

<Description>

Public `https` URL of the file a media header shows. Send it on an `image`, `video`, `gif` or `document` parameter. WhatsApp fetches it at send time, so it must still be reachable then, the same way a free-form media message's `url` must.

</Description>

</Field>

<Field name="location" type="object" prefix="template.components.cards.components.parameters.">

<Description>

The point on the map a location header opens. Send it on a `location` parameter.

</Description>

<FieldChildren kind="request">

<Field name="latitude" type="number" prefix="template.components.cards.components.parameters.location." required>

<Description>

Latitude in decimal degrees.

</Description>

</Field>

<Field name="longitude" type="number" prefix="template.components.cards.components.parameters.location." required>

<Description>

Longitude in decimal degrees.

</Description>

</Field>

<Field name="name" type="string" prefix="template.components.cards.components.parameters.location.">

<Description>

Name of the place, shown above the address.

</Description>

</Field>

<Field name="address" type="string" prefix="template.components.cards.components.parameters.location.">

<Description>

Street address of the place. Shown only when `name` is also set.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="name" type="string" prefix="template.components.cards.components.parameters.">

<Description>

Required when the template declares named parameters: the placeholder this value fills (for example `first_name`), matching exactly one of the names the template declares. Name every parameter in that case; order does not matter once names are supplied. Omit this field for a positional template, which takes its values in `{{n}}` order instead. Sending the wrong set of names, or leaving one out that the template requires, returns a `422` `WhatsAppTemplateParameterMismatch`.

</Description>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

<Field name="text" type="object">

<Description>

Free-form text to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`.

</Description>

<FieldChildren kind="request">

<Field name="body" type="string" prefix="text." required>

<Description>

The message text. The WhatsApp client turns any URL it contains into a clickable link.

</Description>

</Field>

<Field name="preview_url" type="boolean" prefix="text.">

<Description>

Whether the WhatsApp client renders a preview of the first URL in `body`. A URL must begin with `http://` or `https://`, only the first one is previewed, and the client falls back to a plain link when it cannot fetch a preview. Not returned when the message is read back, because WhatsApp does not report whether a preview rendered.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="image" type="object">

<Description>

A free-form image to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`.

</Description>

<FieldChildren kind="request">

<Field name="url" type="string" prefix="image." required>

<Description>

Public `https` URL of the image. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. JPEG and PNG only, up to 5 MB.

</Description>

</Field>

<Field name="caption" type="string" prefix="image.">

<Description>

Text shown beneath the image.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="video" type="object">

<Description>

A free-form video to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`.

</Description>

<FieldChildren kind="request">

<Field name="url" type="string" prefix="video." required>

<Description>

Public `https` URL of the video. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. MP4 with H.264 video and AAC audio, up to 16 MB.

</Description>

</Field>

<Field name="caption" type="string" prefix="video.">

<Description>

Text shown beneath the video.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="audio" type="object">

<Description>

Free-form audio to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`.

</Description>

<FieldChildren kind="request">

<Field name="url" type="string" prefix="audio." required>

<Description>

Public `https` URL of the audio file. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. AAC, AMR, MP3, M4A and OGG (OPUS codec, mono) are supported, up to 16 MB.

</Description>

</Field>

<Field name="voice" type="boolean" prefix="audio.">

<Description>

Whether to send this as a voice note rather than a basic audio message. A voice note auto-downloads, shows the sender's profile picture, and can be transcribed for the recipient. It requires an `.ogg` file encoded with the OPUS codec; any other format makes transcription fail. Leave it false for an ordinary audio attachment.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="sticker" type="object">

<Description>

A free-form sticker to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`.

</Description>

<FieldChildren kind="request">

<Field name="url" type="string" prefix="sticker." required>

<Description>

Public `https` URL of the sticker. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. WebP only: up to 100 KB for a static sticker and 500 KB for an animated one. A sticker carries no caption.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="document" type="object">

<Description>

A free-form document to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`.

</Description>

<FieldChildren kind="request">

<Field name="url" type="string" prefix="document." required>

<Description>

Public `https` URL of the document. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. Up to 100 MB. PDF, Word, Excel, PowerPoint and plain text render reliably in the WhatsApp client; other file types are transmitted but WhatsApp does not support them.

</Description>

</Field>

<Field name="caption" type="string" prefix="document.">

<Description>

Text shown beneath the document.

</Description>

</Field>

<Field name="filename" type="string" prefix="document.">

<Description>

Name the recipient sees, including the extension. WhatsApp derives one from the URL when you omit it.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="location" type="object">

<Description>

A free-form location to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`.

</Description>

<FieldChildren kind="request">

<Field name="latitude" type="number" prefix="location." required>

<Description>

Latitude in decimal degrees.

</Description>

</Field>

<Field name="longitude" type="number" prefix="location." required>

<Description>

Longitude in decimal degrees.

</Description>

</Field>

<Field name="name" type="string" prefix="location.">

<Description>

Name of the place, shown above the address.

</Description>

</Field>

<Field name="address" type="string" prefix="location.">

<Description>

Street address of the place. Shown only when `name` is also set.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="tags" type="array of object">

<Description>

Structured `{name, value}` labels for filtering. Tags become first-class query dimensions: filter the list endpoint by tag name. Maximum 20 tags per send. Use tags for low-cardinality dimensions (`category`, `experiment_variant`). For arbitrary structured context you do not need as a filter dimension, use `metadata` instead.

</Description>

<FieldChildren kind="request">

<Field name="name" type="string" prefix="tags." required>

<Description>

Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.

</Description>

</Field>

<Field name="value" type="string" prefix="tags." required>

<Description>

Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="metadata" type="object">

<Description>

Arbitrary JSON object stored on the message and returned on API reads. Maximum 2 KB serialized. Use metadata for per-send context like internal IDs and foreign keys. For low-cardinality filterable labels, use `tags` instead.

</Description>

</Field>

</Payload>

<Payload kind="response">

<Field name="id" type="string" required>

<Description>

ID of the message (`wam_`-prefixed), assigned when the send is accepted. Pass it as `message_id` to the get-message and list-events endpoints.

</Description>

</Field>

<Field name="direction" type="string" required>

<Description>

Whether the message was sent by the business (`outbound`) or received from the contact (`inbound`).

</Description>

<Description>

Possible values: `outbound`, `inbound`

</Description>

</Field>

<Field name="from" type="object" required>

<Description>

Sender of the message. On outbound messages, the business number it was sent from; on inbound, the WhatsApp contact.

</Description>

<FieldChildren kind="response">

<Field name="phone_number" type="string" prefix="from.">

<Description>

Phone number in E.164 format, when known.

</Description>

</Field>

<Field name="bsuid" type="string" prefix="from.">

<Description>

Business-scoped user ID, Meta's identifier for the WhatsApp user. Present only on the WhatsApp-user side of the message.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="to" type="object" required>

<Description>

Recipient of the message. On outbound messages, the WhatsApp contact; on inbound, the business number.

</Description>

<FieldChildren kind="response">

<Field name="phone_number" type="string" prefix="to.">

<Description>

Phone number in E.164 format, when known.

</Description>

</Field>

<Field name="bsuid" type="string" prefix="to.">

<Description>

Business-scoped user ID, Meta's identifier for the WhatsApp user. Present only on the WhatsApp-user side of the message.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="template" type="object">

<Description>

The template the message was sent from. For authentication templates the filled-in values are not returned.

</Description>

<FieldChildren kind="response">

<Field name="slug" type="string" prefix="template." required>

<Description>

The template's stable handle (for example `bird_otp`).

</Description>

</Field>

<Field name="category" type="string" prefix="template." required>

<Description>

Content classification applied to messages sent from this template.

</Description>

</Field>

<Field name="language" type="string" prefix="template." required>

<Description>

The canonical BCP-47 tag of the template variant that was sent.

</Description>

</Field>

<Field name="components" type="array of object" prefix="template." required>

<Description>

The values that filled the template's placeholders. Empty for an authentication template, whose content is never returned.

</Description>

<FieldChildren kind="response">

<Field name="type" type="string" prefix="template.components." required>

<Description>

Which part of the template this fills in: `body` for the main text, `button` for a button's variable, `header` for the header's text, media or location, `carousel` for the cards.

</Description>

<Description>

Possible values (may grow over time): `header`, `body`, `button`, `carousel`

</Description>

</Field>

<Field name="parameters" type="array of object" prefix="template.components.">

<Description>

The values that fill this part's placeholders. A positional template takes them in `{{n}}` placeholder order; a template with named parameters requires each parameter's `name` to match one the template declares, and order then carries no meaning. Send it on every part except `carousel`, which carries its values on `cards`.

</Description>

<FieldChildren kind="response">

<Field name="type" type="string" prefix="template.components.parameters." required>

<Description>

The kind of value this parameter carries, which decides which of the fields below to send.

</Description>

</Field>

<Field name="text" type="string" prefix="template.components.parameters.">

<Description>

The value substituted into the placeholder, as a plain string. Send it on a `text` parameter.

</Description>

</Field>

<Field name="url" type="string" prefix="template.components.parameters.">

<Description>

Public `https` URL of the file a media header shows. Send it on an `image`, `video`, `gif` or `document` parameter. WhatsApp fetches it at send time, so it must still be reachable then, the same way a free-form media message's `url` must.

</Description>

</Field>

<Field name="location" type="object" prefix="template.components.parameters.">

<Description>

The point on the map a location header opens. Send it on a `location` parameter.

</Description>

<FieldChildren kind="response">

<Field name="latitude" type="number" prefix="template.components.parameters.location." required>

<Description>

Latitude in decimal degrees.

</Description>

</Field>

<Field name="longitude" type="number" prefix="template.components.parameters.location." required>

<Description>

Longitude in decimal degrees.

</Description>

</Field>

<Field name="name" type="string" prefix="template.components.parameters.location.">

<Description>

Name of the place, shown above the address.

</Description>

</Field>

<Field name="address" type="string" prefix="template.components.parameters.location.">

<Description>

Street address of the place. Shown only when `name` is also set.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="name" type="string" prefix="template.components.parameters.">

<Description>

Required when the template declares named parameters: the placeholder this value fills (for example `first_name`), matching exactly one of the names the template declares. Name every parameter in that case; order does not matter once names are supplied. Omit this field for a positional template, which takes its values in `{{n}}` order instead. Sending the wrong set of names, or leaving one out that the template requires, returns a `422` `WhatsAppTemplateParameterMismatch`.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="cards" type="array of object" prefix="template.components.">

<Description>

The values that fill each card of a carousel. Send it only on a `carousel` part. A carousel sends exactly the number of cards its template was approved with, so every card needs an entry.

</Description>

<FieldChildren kind="response">

<Field name="components" type="array of object" prefix="template.components.cards." required>

<Description>

The values that fill this card's blocks.

</Description>

<FieldChildren kind="response">

<Field name="type" type="string" prefix="template.components.cards.components." required>

<Description>

Which part of the card this fills in: `header` for the card's image or video, `body` for its text, `button` for a button's variable.

</Description>

<Description>

Possible values (may grow over time): `header`, `body`, `button`

</Description>

</Field>

<Field name="parameters" type="array of object" prefix="template.components.cards.components.">

<Description>

The values that fill this part's placeholders, in placeholder order.

</Description>

<FieldChildren kind="response">

<Field name="type" type="string" prefix="template.components.cards.components.parameters." required>

<Description>

The kind of value this parameter carries, which decides which of the fields below to send.

</Description>

</Field>

<Field name="text" type="string" prefix="template.components.cards.components.parameters.">

<Description>

The value substituted into the placeholder, as a plain string. Send it on a `text` parameter.

</Description>

</Field>

<Field name="url" type="string" prefix="template.components.cards.components.parameters.">

<Description>

Public `https` URL of the file a media header shows. Send it on an `image`, `video`, `gif` or `document` parameter. WhatsApp fetches it at send time, so it must still be reachable then, the same way a free-form media message's `url` must.

</Description>

</Field>

<Field name="location" type="object" prefix="template.components.cards.components.parameters.">

<Description>

The point on the map a location header opens. Send it on a `location` parameter.

</Description>

<FieldChildren kind="response">

<Field name="latitude" type="number" prefix="template.components.cards.components.parameters.location." required>

<Description>

Latitude in decimal degrees.

</Description>

</Field>

<Field name="longitude" type="number" prefix="template.components.cards.components.parameters.location." required>

<Description>

Longitude in decimal degrees.

</Description>

</Field>

<Field name="name" type="string" prefix="template.components.cards.components.parameters.location.">

<Description>

Name of the place, shown above the address.

</Description>

</Field>

<Field name="address" type="string" prefix="template.components.cards.components.parameters.location.">

<Description>

Street address of the place. Shown only when `name` is also set.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="name" type="string" prefix="template.components.cards.components.parameters.">

<Description>

Required when the template declares named parameters: the placeholder this value fills (for example `first_name`), matching exactly one of the names the template declares. Name every parameter in that case; order does not matter once names are supplied. Omit this field for a positional template, which takes its values in `{{n}}` order instead. Sending the wrong set of names, or leaving one out that the template requires, returns a `422` `WhatsAppTemplateParameterMismatch`.

</Description>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

<Field name="text" type="object">

<Description>

Text the message carried.

</Description>

<FieldChildren kind="response">

<Field name="body" type="string" prefix="text." required>

<Description>

The message text.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="image" type="object">

<Description>

Image the message carried.

</Description>

<FieldChildren kind="response">

<Field name="id" type="string" prefix="image.">

<Description>

ID of the stored file, to pass as `media_id` when fetching it. Absent on an outbound message, whose file we never stored.

</Description>

</Field>

<Field name="url" type="string" prefix="image.">

<Description>

Where to fetch the media. On an inbound message this is a Bird URL you fetch with your API key; it is absent when the file could not be retrieved from WhatsApp. It stays populated after the stored bytes expire, and the link returns `410` from then on. On an outbound message it is the URL the sender supplied, whose availability is the sender's to guarantee.

</Description>

</Field>

<Field name="mime_type" type="string" prefix="image.">

<Description>

Media type WhatsApp reported for the file, for example `image/jpeg`. Absent on outbound messages.

</Description>

</Field>

<Field name="caption" type="string" prefix="image.">

<Description>

Text shown beneath the image. Absent when the sender wrote none.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="video" type="object">

<Description>

Video the message carried.

</Description>

<FieldChildren kind="response">

<Field name="id" type="string" prefix="video.">

<Description>

ID of the stored file, to pass as `media_id` when fetching it. Absent on an outbound message, whose file we never stored.

</Description>

</Field>

<Field name="url" type="string" prefix="video.">

<Description>

Where to fetch the media. On an inbound message this is a Bird URL you fetch with your API key; it is absent when the file could not be retrieved from WhatsApp. It stays populated after the stored bytes expire, and the link returns `410` from then on. On an outbound message it is the URL the sender supplied, whose availability is the sender's to guarantee.

</Description>

</Field>

<Field name="mime_type" type="string" prefix="video.">

<Description>

Media type WhatsApp reported for the file, for example `image/jpeg`. Absent on outbound messages.

</Description>

</Field>

<Field name="caption" type="string" prefix="video.">

<Description>

Text shown beneath the video. Absent when the sender wrote none.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="audio" type="object">

<Description>

Audio the message carried.

</Description>

<FieldChildren kind="response">

<Field name="id" type="string" prefix="audio.">

<Description>

ID of the stored file, to pass as `media_id` when fetching it. Absent on an outbound message, whose file we never stored.

</Description>

</Field>

<Field name="url" type="string" prefix="audio.">

<Description>

Where to fetch the media. On an inbound message this is a Bird URL you fetch with your API key; it is absent when the file could not be retrieved from WhatsApp. It stays populated after the stored bytes expire, and the link returns `410` from then on. On an outbound message it is the URL the sender supplied, whose availability is the sender's to guarantee.

</Description>

</Field>

<Field name="mime_type" type="string" prefix="audio.">

<Description>

Media type WhatsApp reported for the file, for example `image/jpeg`. Absent on outbound messages.

</Description>

</Field>

<Field name="voice" type="boolean" prefix="audio.">

<Description>

Whether this is a voice note rather than an attached audio file. A voice note auto-downloads in the WhatsApp client and can be transcribed for the recipient.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="sticker" type="object">

<Description>

Sticker the message carried.

</Description>

<FieldChildren kind="response">

<Field name="id" type="string" prefix="sticker.">

<Description>

ID of the stored file, to pass as `media_id` when fetching it. Absent on an outbound message, whose file we never stored.

</Description>

</Field>

<Field name="url" type="string" prefix="sticker.">

<Description>

Where to fetch the media. On an inbound message this is a Bird URL you fetch with your API key; it is absent when the file could not be retrieved from WhatsApp. It stays populated after the stored bytes expire, and the link returns `410` from then on. On an outbound message it is the URL the sender supplied, whose availability is the sender's to guarantee.

</Description>

</Field>

<Field name="mime_type" type="string" prefix="sticker.">

<Description>

Media type WhatsApp reported for the file, for example `image/jpeg`. Absent on outbound messages.

</Description>

</Field>

<Field name="animated" type="boolean" prefix="sticker.">

<Description>

Whether the sticker is animated. Absent on an outbound message.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="document" type="object">

<Description>

Document the message carried.

</Description>

<FieldChildren kind="response">

<Field name="id" type="string" prefix="document.">

<Description>

ID of the stored file, to pass as `media_id` when fetching it. Absent on an outbound message, whose file we never stored.

</Description>

</Field>

<Field name="url" type="string" prefix="document.">

<Description>

Where to fetch the media. On an inbound message this is a Bird URL you fetch with your API key; it is absent when the file could not be retrieved from WhatsApp. It stays populated after the stored bytes expire, and the link returns `410` from then on. On an outbound message it is the URL the sender supplied, whose availability is the sender's to guarantee.

</Description>

</Field>

<Field name="mime_type" type="string" prefix="document.">

<Description>

Media type WhatsApp reported for the file, for example `image/jpeg`. Absent on outbound messages.

</Description>

</Field>

<Field name="caption" type="string" prefix="document.">

<Description>

Text shown beneath the document. Absent when the sender wrote none.

</Description>

</Field>

<Field name="filename" type="string" prefix="document.">

<Description>

The sender's own name for the file.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="location" type="object">

<Description>

Location the message carried.

</Description>

<FieldChildren kind="response">

<Field name="latitude" type="number" prefix="location.">

<Description>

Latitude in decimal degrees.

</Description>

</Field>

<Field name="longitude" type="number" prefix="location.">

<Description>

Longitude in decimal degrees.

</Description>

</Field>

<Field name="name" type="string" prefix="location.">

<Description>

Name of the place. Absent when the sender shared a plain pin.

</Description>

</Field>

<Field name="address" type="string" prefix="location.">

<Description>

Street address of the place. Shown only when `name` is also set.

</Description>

</Field>

<Field name="url" type="string" prefix="location.">

<Description>

Link to the place, which WhatsApp includes mainly for business locations. Present on an inbound message when the sender's client supplied one, and absent on a message you sent, since sending a location does not support this field.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="unsupported" type="object">

<Description>

Set when the contact sent content we do not model, naming the WhatsApp content type so the message is not silently empty. Inbound only.

</Description>

<FieldChildren kind="response">

<Field name="type" type="string" prefix="unsupported." required>

<Description>

The WhatsApp content type we did not model. `unsupported` is not a placeholder here: WhatsApp reports its own `unsupported` type for a message its own clients cannot render, and that arrives as this value. Open enum: WhatsApp adds content types over time, so treat an unrecognized value as a future type rather than an error.

</Description>

<Description>

Possible values (may grow over time): `contacts`, `reaction`, `interactive`, `button`, `order`, `system`, `unsupported`

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="status" type="string" required />

<Field name="last_error" type="nullable object">

<Description>

Failure detail for a message that did not reach the recipient. Present only when the message failed.

</Description>

<FieldChildren kind="response">

<Field name="code" type="string" prefix="last_error." required>

<Description>

Failure reason, uniform whether the failure happened internally or was reported by the WhatsApp network. `insufficient_balance`: the workspace could not afford the send. `price_not_found`: no price was configured for this destination/template combination. `internal_error`: an unexpected Bird-side failure. `undeliverable`: the recipient could not be reached (for example not on WhatsApp, or the number is invalid). `service_window_expired`: the 24-hour customer care window has closed and a free-form message cannot be sent; send a template instead. `rate_limited`: the send was throttled. `recipient_suppressed`: the recipient is on the workspace's suppression list; the message was rejected before sending. Open enum: new codes may be added over time, so treat any unrecognized value as a future code rather than an error.

</Description>

<Description>

Possible values (may grow over time): `insufficient_balance`, `price_not_found`, `internal_error`, `undeliverable`, `service_window_expired`, `rate_limited`, `recipient_suppressed`

</Description>

</Field>

<Field name="description" type="string" prefix="last_error." required>

<Description>

Human-readable explanation of the failure.

</Description>

</Field>

<Field name="meta_error_code" type="nullable string" prefix="last_error.">

<Description>

Raw error code from the WhatsApp Cloud API, when available, for low-level debugging.

</Description>

</Field>

<Field name="occurred_at" type="string" prefix="last_error." required>

<Description>

When the failure occurred.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="created_at" type="string" required>

<Description>

When the message was accepted for delivery.

</Description>

</Field>

<Field name="sent_at" type="nullable string">

<Description>

When the message was handed to the WhatsApp network. Null until then.

</Description>

</Field>

<Field name="delivered_at" type="nullable string">

<Description>

When delivery was confirmed. Null until then.

</Description>

</Field>

<Field name="read_at" type="nullable string">

<Description>

When the message was read by the recipient. Null until then.

</Description>

</Field>

<Field name="cost" type="nullable object">

<Description>

What the message cost, split into Bird's charge and any third-party fees passed through. Null on an inbound message, which is never priced, on an outbound message that has not been priced yet, and on one rejected before pricing. The rate depends on the message category and the recipient's country.

</Description>

<FieldChildren kind="response">

<Field name="amount" type="string" prefix="cost." required>

<Description>

Total charged, as a decimal string: the sum of the components below. Net of tax, which applies to your wallet balance rather than to an individual charge.

</Description>

</Field>

<Field name="currency_code" type="string" prefix="cost." required>

<Description>

ISO 4217 currency code. Every component is denominated in this currency.

</Description>

</Field>

<Field name="transaction_amount" type="nullable string" prefix="cost." required>

<Description>

What Bird charged to carry the message, as a decimal string. Null when this component was not priced; `"0.00000"` when it priced at zero.

</Description>

</Field>

<Field name="passthrough_amount" type="nullable string" prefix="cost." required>

<Description>

Third-party fees Bird passes on, as a decimal string, such as US 10DLC carrier surcharges. Null when this component was not priced; `"0.00000"` when it priced at zero.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="tags" type="array of object">

<Description>

Structured `{name, value}` filter labels applied to this message.

</Description>

<FieldChildren kind="response">

<Field name="name" type="string" prefix="tags." required>

<Description>

Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.

</Description>

</Field>

<Field name="value" type="string" prefix="tags." required>

<Description>

Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="metadata" type="object">

<Description>

Arbitrary JSON metadata stored on the message.

</Description>

</Field>

</Payload>