whatsapp-messages

Send a WhatsApp message

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, location, contact_cards or interactive. 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. Bird tracks that window, so a send into a closed one is refused with a 422 WhatsAppServiceWindowClosed before anything is created or charged; send a template instead, which reopens the window once the contact replies. A window that closes between accept and dispatch still fails asynchronously, carrying service_window_expired on the message's last_error. Every free-form send requires from.

Interactive content gives the recipient something to tap. interactive names its kind in type and carries that kind's own field: reply buttons, a list menu, a cta_url link button, or cards for a carousel; location_request_message and request_contact_info are each a single button asking the recipient for something, so body_text is the whole message. A tap on a reply button or a list row comes back as an inbound message carrying interactive_reply. The other kinds answer in their own shape: a cta_url link opens in the recipient's browser and sends nothing back, and the two request kinds come back as the thing they asked for, an inbound location or contact_cards message. Interactive content is free-form, so the customer service window and the from requirement above both apply.

Contact cards share up to five contacts in one message. Each card's name needs formatted_name plus at least one other part, and a phone_number in E.164 earns that card a button opening a chat with it. Contact cards are free-form too, so the same window and from rules apply.

A group send addresses a WhatsApp group ID in to and carries no from: the group sends on its own number, and the response reports the fan-out. recipient_count is the group's membership when the send was accepted, delivered_count and read_count count against it, and status turns delivered only once every participant has it. WhatsApp delivers neither interactive content nor an authentication template to a group, and a Bird-managed template sends from a number no group is scoped to.

Set in_reply_to_message_id to quote a message the contact sees above this one, the way replying in the WhatsApp client does. Any content quotes, and the quoted message must be one from this same conversation.

The 202 response is the accepted message, echoing the resolved content; it is not a delivery confirmation. Follow delivery with Get a WhatsApp message, the per-message timeline from List events for a WhatsApp message, or whatsapp.* webhook events.

Each of these returns a 422:

  • 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.
  • A recipient that is neither a valid phone number nor a business-scoped user ID.
  • Free-form content sent into a closed customer service window (WhatsAppServiceWindowClosed).
  • Interactive content or an authentication template addressed to a group (WhatsAppGroupContentNotSupported).

A group to naming no group this workspace holds fails with a 404 WhatsAppGroupNotFound, and one whose group is not active with a 409 WhatsAppGroupNotActive. A send from a workspace with no wallet balance fails with a 402.

post/v1/whatsapp/messages

Request

  • Base URL: https://us1.platform.bird.com
  • URL: https://us1.platform.bird.com/v1/whatsapp/messages
  • Auth: one of:
    • HTTP bearer
    • API key in cookie bird_session

Headers

Idempotency-Keystring

Client-supplied key. On operations supporting request deduplication, a retained response is replayed for duplicate requests with the same key within the idempotency window (3 hours by default). This protection requires a workspace, organization, or staff-account scope. User-only and unscoped unauthenticated operations, streams, and operations with a separate replay contract do not use this response replay.

On a supported operation, if idempotency protection is unavailable before execution, the API returns 503 IdempotencyUnavailable (E01033) without executing this attempt. Retry with backoff using the same key and request. An operation that takes effect before its response is retained can still execute again on retry.

Two distinct 409 errors signal misuse:

  • request_in_progress (E01004): The same key is currently being processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds.
  • idempotency_key_reuse (E01005): The same key has already completed against a different request body or method. Generate a new key.

Recommended key format is <event-type>/<entity-id> (for example welcome-user/usr_abc123).

X-Workspace-Idstring

Workspace context for the request. Required for dashboard authentication and master keys. Workspace keys and access tokens already identify their workspace; send that workspace or omit the header. A different one is rejected.

X-Organization-Idstring

Organization context for the request. Required for dashboard authentication. An API key or access token carries its own organization, so send either that organization or no header at all; a different one is rejected.

Request body

WhatsAppMessageSendRequest required(unresolved $ref)

Response

Message accepted for asynchronous delivery.

idstring required
direction'outbound' | 'inbound' required

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

in_reply_to_message_idstring
status'scheduled' | 'accepted' | 'sent' | 'delivered' | 'failed' | 'rejected' | 'canceled' | 'received' required

Delivery status:

  • accepted: Accepted and queued for sending.
  • sent: Handed to the WhatsApp network.
  • delivered: Confirmed as delivered to the recipient's device.
  • failed: Permanently failed.
  • rejected: Refused before sending and not charged.
  • received: Received as an inbound message.
  • scheduled: Reserved and not returned.
  • canceled: Reserved and not returned.

Read receipts appear in read_at and whatsapp.read events, in both directions: the recipient opening an outbound message, and the business acknowledging an inbound one.

originstring

Where a message came from when it did not come through Bird live: its provenance. It does not say who answers the contact. On a number shared with the WhatsApp Business app, a message can come from the app while Bird keeps answering.

  • business_agent_echo: the Meta Business Agent on this number composed it.
  • business_app_echo: someone sent it from the WhatsApp Business app on a number shared with it.
  • history_sync: WhatsApp copied it, in either direction, from the WhatsApp Business app's chat history when the number was shared. Neither /v1/whatsapp/messages nor a webhook event reports an imported message, so neither carries this value.
recipient_countinteger

How many recipients a group send was addressed to, taken when the send was accepted. It is the group's membership at that moment, not its membership now: someone joining through the invite link while the message is in flight does not receive it and does not change this count.

Absent on a one-to-one message, along with delivered_count and read_count. A message with one recipient has no fan-out to report, and its delivery is what status, delivered_at and read_at already say. Absent for the same reason on a group message sent before Bird recorded the count, and on a send to a group nobody had joined yet: there is no denominator to report, and none can be recovered after the fact, since membership has moved on. to.group_id is what tells a group message from a one-to-one one in every case, including those two. With no denominator to resolve against, status is read as stored, the way a one-to-one message's is: it reaches sent when the message is handed to WhatsApp and stops there, because delivery is confirmed per participant and a send with no participants collects no confirmations.

It is also the denominator status is resolved against: on a group message status reports the furthest point every recipient has reached, so it turns delivered only once delivered_count equals this number, and stays sent while some have confirmed and others have not. failed and rejected are never per recipient: there is one hand-off to the WhatsApp network and one way for that to be refused. delivered_at and read_at are the first recipient's, not the last.

delivered_countinteger

How many of the recipient_count recipients WhatsApp has confirmed the message reached. A recipient who reported only a read counts here too: WhatsApp skips the delivery receipt when someone is already looking at the chat, so waiting for one would leave that person uncounted for ever.

Absent on a one-to-one message, which has no fan-out to count, and on a group message with no recipient_count to count against.

read_countinteger

How many of the recipient_count recipients have opened the message. Read receipts do not move status, which has no read value; they surface here and in read_at.

Absent on a one-to-one message, which has no fan-out to count, and on a group message with no recipient_count to count against.

created_atstring date-time required

When the message was accepted for delivery.

sent_atstring date-time nullable

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

delivered_atstring date-time nullable

When delivery was confirmed. Null until then.

read_atstring date-time nullable

When the message was read. On an outbound message this is the recipient opening it. On an inbound one it is when Bird acknowledged the message to WhatsApp for the business, which a read receipt sets. Null until then.

metadataobject

Arbitrary JSON metadata stored on the message.

Changes

No recorded changes to this operation across all 2 revisions of this API.