WhatsApp service messages
A service message is anything you send that isn't a pre-approved template: the free-form content a business sends inside an open conversation. POST /v1/whatsapp/messages carries exactly one of eight service-message content arms, or a template. This page covers what the eight arms share; each arm's own page covers its wire shape and its own limits.
The content arms
| Type | Field | What it carries | Use it when |
|---|---|---|---|
| Plain text | text | A body up to 4096 characters, with an optional link preview | you're sending a message with no attachment |
| Images | image | A public image URL and an optional caption | you're sending a photo or a graphic |
| Video | video | A public video URL and an optional caption | you're sending a video clip |
| Audio | audio | A public audio URL, optionally rendered as a voice note | you're sending a voice message or an audio clip |
| Stickers | sticker | A public WebP image URL | you're sending a sticker |
| Documents | document | A public file URL, an optional caption, and an optional filename | you're sending a PDF, a spreadsheet, or another file |
| Location | location | A latitude and longitude, with an optional name and address | you're sending a pin, such as a pickup point |
| Interactive messages | interactive | Body text plus a button, a menu, a link, a card, or a request for location or contact | you want the recipient to tap something instead of typing a free reply |
A request carries exactly one of template or one of these eight fields. A request that carries none of them, or more than one, is refused with a 422.
The customer service window
A service message, meaning any of the eight arms above, delivers only inside an open 24-hour customer service window. The contact opens that window by messaging or calling your business number, and every further message from them resets it to 24 hours.
A service message sent into a closed window is refused up front: the request returns a 422 E15044 WhatsAppServiceWindowClosed, and nothing is created or charged. Send an approved template instead; it reaches the contact regardless of the window, and their reply reopens it. A window that closes in the moment between accept and dispatch still fails, but asynchronously: the message reaches failed with service_window_expired on last_error.
The accept-time check is best effort, not a guarantee: the gate fails open, so a store miss or a read error lets the send through rather than blocking it. A 202 is therefore not proof the window was open when the send went out; the terminal signal is the message's own status, not the accept response.
Every service message also requires from, a number your workspace owns. Bird's managed numbers cannot carry it, so a service message needs a number of your own connected first; see Phone number setup.
See the customer service window for the full lifecycle: how the window opens, what resets it, and how it's tracked.
Sending media by URL
image, video, audio, sticker, and document all take a url pointing at a file WhatsApp fetches at send time, rather than a file you upload to Bird. Bird checks the URL's shape at accept, before anything is enqueued:
- Non-empty and parseable, with a host and no raw space
- Scheme is https
An http URL is rejected with a 422 at accept, even though WhatsApp itself would fetch it fine. That's Bird policy, not a limit WhatsApp imposes.
Bird does not check the file's size, its MIME type, or whether the URL is reachable. WhatsApp fetches the URL itself, once it dispatches the message, so a signed URL has to stay valid past that moment, not just at the time you send the request; a private or expired URL fails once WhatsApp tries to fetch it. WhatsApp also caches a fetched URL for roughly 10 minutes, so resending the identical URL inside that window re-serves the first fetch rather than fetching again.
When media fails
A media send follows the same async path as any WhatsApp message: Bird returns 202 and accepts the message, then WhatsApp fetches the URL when it dispatches. If that fetch fails, the message reaches failed with media_rejected on last_error, which is Meta's 131053 underneath.
media_rejected is an umbrella covering an oversize file, a 404, a DNS failure, and a wrong MIME type alike; Bird doesn't split it further, so don't expect a distinct code per cause.
An asynchronously failed media send is still charged. Charging happens when Bird processes the accepted send, before WhatsApp ever fetches the URL, and there's no refund path once that charge lands. Budget accordingly: a message that fails later in media_rejected already cost the same as one that delivered.
Reading what a contact sent
An inbound message carries one of the same eight arms, so the field you read matches the type the contact used. Receiving WhatsApp messages covers reading inbound messages over the API, fetching the media a contact sent, and the whatsapp.received webhook.
Next steps
- How sending works: the request envelope, the 202 model, and safe retries
- Interactive messages: the six types a recipient can tap
- Receiving WhatsApp messages: inbound messages, media, and the whatsapp.received webhook
- WhatsApp templates: the messages you can still send once the window is closed