Carousel Messages
Carousel messages display multiple cards that recipients can scroll through horizontally. Each card can include a title, subtitle, image, and action buttons. They are ideal for showcasing products, plans, or options.
Required Scopes
| Scope | Purpose |
|---|---|
messages:create | Send carousel messages |
Channel Support
| Channel | Support | Implementation | Request fields |
|---|---|---|---|
| Yes | Approved carousel template | template_name, language, cards | |
| Messenger | Yes | Generic template | elements |
| Yes | Generic template | elements | |
| RCS | No | Not through this endpoint; see RCS | |
| Telegram | No | Not supported | |
| SMS | No | Not supported | |
| No | Not supported |
Send a Carousel
POST /api/v1/messages/send/carousel
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
channel_id | string | Yes | The channel to send through |
contact_id | string | Yes | The recipient contact |
elements | array | Messenger, Instagram | Carousel cards (1-10) |
template_name | string | Name of the approved carousel template | |
language | string | No | WhatsApp template language (default en) |
cards | array | Per-card values for the template (max 10) | |
meta | object | No | Your own metadata, stored on the message |
Send either elements (Messenger, Instagram) or template_name + cards (WhatsApp), never both.
Element object (Messenger and Instagram):
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Card title, max 80 characters |
subtitle | string | No | Card description, max 80 characters |
image_url | string | No | Absolute http(s) URL of the card image |
default_action | object | No | What happens when the card itself is tapped: {"type": "web_url", "url": "https://..."} |
buttons | array | No | Up to 3 buttons per card |
Button object:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | web_url (opens a link), postback (sends payload back to you) or phone_number (Messenger only) |
title | string | Yes | Button label, max 20 characters |
url | string | web_url | Absolute http(s) URL |
payload | string | postback | Your value, max 1000 characters. It comes back when the contact taps the button |
curl
curl -X POST "https://api.sendseven.com/api/v1/messages/send/carousel" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "channel_insta_001",
"contact_id": "contact_xyz789",
"elements": [
{
"title": "Wireless Headphones",
"subtitle": "Premium noise-cancelling, EUR 149",
"image_url": "https://shop.example.com/images/headphones.jpg",
"default_action": {"type": "web_url", "url": "https://shop.example.com/headphones"},
"buttons": [
{"type": "web_url", "title": "View Product", "url": "https://shop.example.com/headphones"},
{"type": "postback", "title": "Add to Cart", "payload": "add_headphones"}
]
},
{
"title": "Bluetooth Speaker",
"subtitle": "Portable and waterproof, EUR 79",
"image_url": "https://shop.example.com/images/speaker.jpg",
"buttons": [
{"type": "web_url", "title": "View Product", "url": "https://shop.example.com/speaker"},
{"type": "postback", "title": "Add to Cart", "payload": "add_speaker"}
]
}
]
}'
Python
import requests
BASE_URL = "https://api.sendseven.com/api/v1"
HEADERS = {
"Authorization": "Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00",
"Content-Type": "application/json",
}
response = requests.post(
f"{BASE_URL}/messages/send/carousel",
headers=HEADERS,
json={
"channel_id": "channel_msngr_001",
"contact_id": "contact_xyz789",
"elements": [
{
"title": "Basic Plan",
"subtitle": "Perfect for small teams",
"image_url": "https://example.com/images/basic.jpg",
"buttons": [
{"type": "web_url", "title": "Learn More", "url": "https://example.com/basic"},
{"type": "postback", "title": "Choose Basic", "payload": "SELECT_BASIC"},
],
},
{
"title": "Scale Plan",
"subtitle": "For growing businesses",
"image_url": "https://example.com/images/scale.jpg",
"buttons": [
{"type": "web_url", "title": "Learn More", "url": "https://example.com/scale"},
{"type": "postback", "title": "Choose Scale", "payload": "SELECT_SCALE"},
],
},
],
},
)
print(response.json())
WhatsApp request
WhatsApp carousels fill an approved carousel template. Each entry in cards matches one template card, in order:
{
"channel_id": "channel_wa_001",
"contact_id": "contact_xyz789",
"template_name": "product_catalog",
"language": "en",
"cards": [
{
"header": {"type": "image", "url": "https://shop.example.com/images/headphones.jpg"},
"body_parameters": [{"type": "text", "text": "Wireless Headphones"}],
"buttons": [{"type": "url", "index": 0, "parameters": [{"type": "text", "text": "headphones"}]}]
}
]
}
A card header takes either url or media_id; each card has at most 2 buttons.
Response
201 Created:
{
"id": "msg_car_001",
"conversation_id": "conv_def456",
"external_id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlE",
"status": "sent",
"platform": "instagram",
"cards_count": 2,
"error": null
}
Instagram
Instagram shows a carousel as a horizontally scrollable row of cards in the Direct inbox. SendSeven checks Instagram's rules before sending, so a carousel Instagram would reject never leaves your account:
| Rule | Limit |
|---|---|
| Cards per carousel | 1 to 10 |
| Card title | Required, max 80 characters |
| Card subtitle | Max 80 characters |
| Card content | Each card needs a subtitle, image_url, default_action or button besides its title |
| Buttons per card | Max 3 |
| Button types | web_url and postback only |
| Button label | Max 20 characters |
| Postback payload | Max 1000 characters |
Card tap (default_action) | web_url with a url only |
| Images | Absolute http(s) URL; use PNG or JPEG up to 8 MB |
Good to know:
- Carousels show in the Instagram mobile app. Instagram doesn't display them on desktop.
- Like any Instagram message, a carousel can only be sent within 24 hours of the contact's last message.
- Messenger-only options are rejected on Instagram rather than dropped silently:
phone_numberbuttons anddefault_action.webview_height_ratio.
When a rule is broken the request returns 400 with a code in detail, before anything is sent:
{
"detail": {
"error": "CAROUSEL_BUTTON_UNSUPPORTED",
"message": "elements[0].buttons[1].type 'phone_number' is not supported on Instagram; use web_url or postback.",
"field": "elements[0].buttons[1].type",
"channel_type": "instagram"
}
}
detail.error | Meaning |
|---|---|
CAROUSEL_LIMIT_EXCEEDED | Too many cards or buttons, or a text is too long |
CAROUSEL_ELEMENT_INCOMPLETE | A required value is missing, or a card has only a title |
CAROUSEL_BUTTON_UNSUPPORTED | A button type Instagram can't show |
CAROUSEL_FIELD_UNSUPPORTED | A field Instagram can't show (for example webview_height_ratio) |
CAROUSEL_INVALID_URL | image_url, default_action.url or a button url is not an absolute http(s) URL |
field points at the value to fix.
Button taps
A tap on a postback button (Messenger or Instagram) arrives like any other button reply: an inbound message.received event whose text is the button label, with the button's payload in meta.button_id, a {"type": "button_reply", "button_id": "...", "button_title": "..."} attachment and the structured meta.button object (see Webhook events). Flows and bots can branch on it. web_url buttons open the link and send nothing back.
Error Responses
Most errors return detail as a message string. See Error Handling for the response shapes.
| Status | detail | Description |
|---|---|---|
| 400 | "Carousel messages are only supported on WhatsApp, Messenger and Instagram. ..." | The channel does not support carousels |
| 400 | "WhatsApp carousels require 'template_name' and 'cards' fields", "Messenger carousels require 'elements' field", "Instagram carousels require 'elements' field" | Required fields for the channel are missing |
| 400 | "elements[0].image_url is not a valid http(s) URL: ..." | A Messenger element URL is not an absolute http(s) URL |
| 400 | {"error": "CAROUSEL_...", "message": "...", "field": "..."} | The carousel breaks an Instagram rule (see Instagram) |
| 400 | "Contact does not have a WhatsApp ID or phone number", "Contact does not have a Messenger PSID", "Contact does not have an Instagram ID for this channel" | The contact can't be reached on this channel |
| 400 | {"message": "...", "message_id": "...", "error_code": ...} | The platform rejected the carousel. The message is stored as failed and the message.failed webhook fires once; error_code is the platform's code when there is one |
| 401 | "Invalid or expired API token" | Token is invalid or expired |
| 403 | "Missing required permission(s): messages:create" | Token lacks the scope |
| 404 | "Channel not found", "Contact not found" | Unknown ID, or it belongs to another workspace |
| 409 | {"error": "CHANNEL_DISCONNECTED", ...} | The channel is disconnected |
| 409 | {"error": "CONTACT_BLOCKED", ...} | The contact is blocked |
| 422 | list of field errors | Invalid request body (for example more than 10 cards, more than 3 buttons, or a title over 80 characters) |
WhatsApp carousels need an approved carousel template, so they can be sent outside the 24-hour window. Messenger and Instagram carousels need no template, but Meta accepts them only inside the platform's messaging window. SendSeven does not block them up front: Meta decides, and a rejected carousel ends as failed.
Next Steps
- Interactive Messages -- button and list messages
- Channel Capabilities -- what each channel supports
- Instagram -- Instagram channel guide