Carousels
A carousel template sends a swipeable row of cards below the message body. Each card has its own media header, its own body text, and its own buttons — so each card can carry its own variables.
POST /v1/whatsapp/sendCard values go in variables.cards, an object keyed by each card’s position in the message. The first card is "1".
This page covers media card carousels (cards with an image or video header). For carousels whose cards are products from your Meta catalog, see Product Carousels.
Discovering card variables
Fetch the template and read expected_variables. Carousel templates add a cards object, keyed by card position, with the same body and buttons arrays you already know from the top level:
{
"name": "succulents_carousel",
"language": "en_US",
"expected_variables": {
"header": [],
"body": [{ "param_name": "name", "example": "Pablo" }],
"buttons": [],
"cards": {
"1": {
"body": [{ "param_name": 1, "example": "Aloe" }],
"buttons": [{ "param_name": 1, "example": "aloe" }]
},
"2": {
"body": [{ "param_name": 1, "example": "Cactus" }],
"buttons": [{ "param_name": 1, "example": "cactus" }]
}
}
}
}Every card the template was approved with appears, even one with no variables of its own — so Object.keys(expected_variables.cards) tells you the card count. Templates without a carousel have no cards key at all.
The cards object
Keys are card positions — whole numbers as strings, starting at "1" — and they line up exactly with the positions in expected_variables.cards. Each card accepts:
| Field | Type | Description |
|---|---|---|
body | object | Body values for this card, keyed by the param_names from expected_variables.cards["N"].body |
buttons | object | URL button values for this card, keyed by the button’s position within that card, as a string |
product_retailer_id | string | Product carousels only — see Product Carousels |
A card position that is not a whole number of at least 1 — "0", "2.5", "first" — is rejected with a 400. Button keys inside a card are more forgiving: anything that is not a positive whole number is silently dropped.
Example
A two-card carousel with one body variable per card and one dynamic URL button per card:
cURL
curl -X POST https://api.bahasha.app/v1/whatsapp/send \
-H "Authorization: Bearer bh_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"to": "+254700000000",
"phone_number_id": "123456789",
"template_name": "succulents_carousel",
"language_code": "en_US",
"variables": {
"body": { "name": "Pablo" },
"cards": {
"1": { "body": { "1": "Aloe" }, "buttons": { "1": "aloe" } },
"2": { "body": { "1": "Cactus" }, "buttons": { "1": "cactus" } }
}
}
}'The response is the standard send response — see Send a Message.
How many cards get sent
A media carousel sends exactly the cards the template was approved with — between 2 and 10. The card count comes from the template, not from your request:
- Positions in
variables.cardspast the last approved card are ignored. - Omitting
variables.cardsentirely still sends the carousel, with each card’s approved media and text and no variable values filled in.
Only product carousels choose their card count at send time. See Product Carousels.
Card URL buttons
Card buttons follow the same contract as top-level button variables: only URL buttons take a send-time value, that value fills the single positional variable at the end of the button’s link, and it is percent-encoded automatically. You may pass either the suffix alone ("aloe") or the full URL — the base is stripped for you.
The key is the button’s position within that card, as a 1-based string. Values for a card’s quick-reply buttons, or for URL buttons with no variable, are silently ignored.
Carousel card media
There is no per-card media override in the API. media_url and media_id on POST /v1/whatsapp/send apply to the top-level template header only — each carousel card uses the media it was approved with. To change a card’s image or video, edit the template in the Bahasha Dashboard .
Named templates and card bodies
A carousel card’s body placeholders are always numbered ({{1}}, {{2}}), even when the template itself uses named variables — WhatsApp does not allow named parameters inside a card. So a card’s body keys are always "1", "2", and so on, while the template’s top-level body may still use names:
"variables": {
"body": { "name": "Pablo" },
"cards": { "1": { "body": { "1": "Aloe" } } }
}Bahasha handles the rest: on a named template, WhatsApp additionally requires every text parameter in the message — including the card’s numbered ones — to carry a parameter name, and Bahasha adds it for you. Without it the whole message would be rejected with “Parameter name is missing or empty (for card_index=N)”. This holds however the template is shaped, including one whose only variables are on its cards.