Interactive messages
Send Apple Messages for Business quick replies, list pickers, time pickers, and dynamic forms — and read the customer's reply.
Apple Messages for Business supports rich, tappable messages that render natively in the Messages app. Send them through the standard POST /messages endpoint with a top-level interactive field and a kind:
kind |
What the customer sees |
|---|---|
quick_reply |
A prompt with up to a few tappable options |
list_picker |
One or more sections of selectable rows (single or multi-select) |
time_picker |
A set of appointment time slots to choose from |
form |
A multi-page dynamic form (text inputs, pickers, dates) |
apple_pay |
An Apple Pay payment request |
authenticate |
An OAuth2 "Sign in" request |
imessage_app |
A custom iMessage extension bubble |
Addressing
Interactive messages are 1:1 only (no groups). Send from an Apple Messages for Business sender and address the customer by their opaque Apple Messages for Business identifier:
{
"from": "support",
"to": "opaque-amb-customer-id",
"interactive": { "kind": "quick_reply", "...": "..." }
}Two endpoints accept this exact body — pick whichever matches how you track senders. Both share one implementation and one field set:
POST /v4/messages— one-shot send. Passfromin the body; the API finds or creates the Apple Messages for Business chat for you.POST /v4/channels/{channel}/messages— channel-scoped send. The sender lives in the URL ({channel}is the Apple Messages for Business alias, e.g./v4/channels/support/messages), so the body omitsfrom.
If you already hold the chat, reply with POST /v4/chats/{chatId}/messages — the chat already knows its channel, so you omit from there too.
Request fields
Every interactive send is built from three top-level fields. Open the POST /v4/messages reference to see them (and their JSON schema) in the interactive API explorer.
| Field | Type | What it is |
|---|---|---|
from |
string | Which of your senders the message goes out from. It is one string, and for Apple Messages for Business it is the channel's alias — a short, stable name (like support) you assign to your connected Apple Messages for Business account in the dashboard, not a phone number. (A phone number or a ch_… channel id are also valid from values for other channel types.) Blooio resolves the alias to the underlying Apple Messages for Business channel at send time, so you can transfer the alias to a replacement integration without changing your code. Omit from on the channel-scoped endpoint (it's in the path) and on the chat endpoint (the chat fixes it). |
to |
string | Who receives the message. The customer's opaque Apple Messages for Business identifier — the id Apple issues for that customer, handed to you on the inbound message.received webhook that opened the conversation. Apple Messages for Business is always customer-initiated, so you have this value before you ever reply. It is not a phone number or email. |
interactive |
object | The interactive content itself. Its kind selects the experience (quick_reply, list_picker, …) and the rest of the object depends on that kind. See Common fields and each kind's section below; the full machine-readable schema is the interactive field on POST /v4/messages. |
Common fields
These apply to every kind:
summary_text(ortext) — a plain-text fallback shown where interactive UI can't render. It is also the quick reply prompt.received_message— the bubble the customer sees before interacting (title,subtitle,style, optionalimageIdentifier).reply_message— the bubble shown after they reply.request_identifier— an optional stable id echoed back on the reply (defaults to the message id).images— PNGs referenced byimageIdentifier, each{ "identifier", "url" }(a public https URL Blooio fetches) or{ "identifier", "data" }(base64). Each image must be a real PNG under 200 KB, and all of them together under 5 MB.
Note
quick_replymust not includereceived_message/reply_message— Apple rejects the send. Send a plain text bubble first to introduce the prompt, then the quick reply.
Quick reply
curl -X POST https://api.blooio.com/v4/messages \
-H "Authorization: Bearer bl_live_..." \
-H "Content-Type: application/json" \
-d '{
"from": "support",
"to": "opaque-amb-customer-id",
"interactive": {
"kind": "quick_reply",
"summary_text": "How can we help?",
"items": [
{ "id": "sales", "title": "Talk to sales" },
{ "id": "support", "title": "Get support" },
{ "id": "hours", "title": "Store hours" }
]
}
}'Try itReading that request line by line — every field maps to the POST /v4/messages reference:
POST https://api.blooio.com/v4/messages— the one-shot send endpoint. To send from the sender in the URL instead, callPOST /v4/channels/support/messagesand dropfromfrom the body.Authorization: Bearer bl_live_...— your Blooio API key; every call is authenticated as a bearer token, and the key's organization scopes which sendersfromcan resolve."from": "support"— the Apple Messages for Business sender alias. This is the one field that says who the message is from;supportresolves to your connected Apple Messages for Business channel."to": "opaque-amb-customer-id"— the customer's opaque Apple Messages for Business id from the inbound webhook. This is who receives it."interactive"— the flat content field. Its presence makes this an interactive message (instead oftext,attachments, etc.)."kind": "quick_reply"— render a quick reply. Every other field insideinteractiveis interpreted relative to thiskind."summary_text"— the prompt shown above the buttons and the plain-text fallback where the UI can't render."items"— the tappable options. Eachid(identifierworks too) comes back asselected.idon the customer's reply so you know what they picked; eachtitleis the button label.
A successful call returns a queued message (id, chat_id, channel_id, status). Track delivery with GET /v4/chats/{chatId}/messages/{messageId}/events or a webhook.
List picker
Sections of selectable rows. Set multipleSelection to let the customer choose more than one row in a section.
{
"kind": "list_picker",
"received_message": { "title": "Choose a service", "style": "small" },
"reply_message": { "title": "Thanks — booking you in", "style": "small" },
"sections": [
{
"title": "Services",
"multipleSelection": false,
"items": [
{ "identifier": "haircut", "title": "Haircut", "subtitle": "30 min" },
{ "identifier": "color", "title": "Color", "subtitle": "90 min" }
]
}
]
}Each row's identifier (id works too) comes back as selected.id on the reply, so branch on it rather than on the title. A row without one is numbered "1", "2", … within its section.
Time picker
Offer appointment slots. startTime accepts ISO-8601 (it is normalized to Apple's format on send); duration is in seconds.
{
"kind": "time_picker",
"received_message": { "title": "Pick a time for your consultation" },
"event": {
"title": "Consultation",
"location": { "title": "Main St Clinic" },
"timeslots": [
{ "identifier": "slot-1", "startTime": "2026-07-20T17:00:00Z", "duration": 3600 },
{ "identifier": "slot-2", "startTime": "2026-07-20T18:00:00Z", "duration": 3600 }
]
}
}The chosen slot's identifier comes back as selected.id. A slot without one is numbered "1", "2", … in order.
Dynamic form
A form is a multi-page flow (text inputs, list/date pickers, a summary). The dynamic object is forwarded to Apple's Dynamic Interactive Message payload verbatim, so it uses Apple's field names (pages, startPageIdentifier, requestIdentifier, per-page textInputs / datePicker / listPicker, etc.).
{
"kind": "form",
"received_message": { "title": "Book an appointment" },
"reply_message": { "title": "Got it — we'll confirm shortly" },
"dynamic": {
"version": "1.0",
"requestIdentifier": "booking-42",
"startPageIdentifier": "details",
"showSummary": true,
"pages": [
{
"pageIdentifier": "details",
"title": "Your details",
"nextPageIdentifier": "when",
"textInputs": [
{ "identifier": "name", "title": "Full name", "type": "name" },
{ "identifier": "email", "title": "Email", "type": "email" }
]
},
{
"pageIdentifier": "when",
"title": "Preferred date",
"datePicker": { "identifier": "date", "title": "Date" }
}
]
}
}You can also supply the form object under form instead of dynamic.
Apple Pay & authentication
kind: "apple_pay"— put Apple's payment request underpayment. The outcome arrives aspayment.received,payment.completed, andpayment.failedwebhook events; see Apple Pay.kind: "authenticate"— put the OAuth2 request underauthenticate(or a bareoauth2object). Sent as interactive data version 2.0 via Apple's authenticate endpoint.
Custom iMessage app
kind: "imessage_app" renders a custom Messages extension. It requires the extension's own bundle id in bid (com.apple.messages.MSMessageExtensionBalloonPlugin:{team-id}:{ext-bundle-id}):
{
"kind": "imessage_app",
"bid": "com.apple.messages.MSMessageExtensionBalloonPlugin:TEAMID:com.brand.ext",
"app": { "app_id": 123, "app_name": "Brand App" },
"url": "?data=abc",
"use_live_layout": true,
"received_message": { "title": "Open the app", "subtitle": "Tap to continue" }
}Reading the reply
A reply arrives as a message.received webhook with message_type interactive. It has no plain body, so Blooio fills text with a readable summary (the picked titles or the form answers, joined with commas) and adds an interactive object that says exactly what the customer picked. GET /chats/{chatId}/messages/{messageId} returns the same selection on the message.
| Field | On | What it is |
|---|---|---|
kind |
Every reply | quick_reply, list_picker, time_picker, form, or invitation |
chosen |
Every reply | The picked titles in order: option titles, the slot's start time, or the form answers |
selected |
Quick reply, list picker, time picker | { "id", "title" } for a single pick. id is the id or identifier you set on the option. A quick reply adds index, the option's position in items |
selections |
List picker | An array of { "id", "title" } in place of selected when the customer picked more than one row |
responses |
Form | One entry per answered page: page_identifier, question, and answers (identifier, type, title, value) |
request_identifier |
Webhook | The request_identifier you sent, or the request message's id without its msg_ prefix |
detail |
Webhook | Apple's raw reply object for the kind |
role, label |
REST | role: "reply" and a display label such as List Picker |
Branch on selected.id, or on each selections[].id. Titles are for display: they change when you reword an option, and text is only a summary.
app.post('/webhooks/blooio', (req, res) => {
const { type, data } = req.body;
const reply = data.interactive;
if (type === 'message.received' && data.channel_type === 'amb' && reply) {
const picks = reply.selections ?? (reply.selected ? [reply.selected] : []);
for (const { id } of picks) addToCart(data.chat_id, id);
}
res.sendStatus(200);
});Quick reply
{
"id": "evt_018f7b2d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"type": "message.received",
"api_version": "2026-10-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2d-6f7a-7b8c-9d0e-2f3a4b5c6d7e",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "Track an order",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "quick_reply",
"request_identifier": "3c2b1a09-8f7e-4d6c-9b5a-4e3d2c1b0a9f",
"chosen": ["Track an order"],
"selected": { "id": "track", "index": 0, "title": "Track an order" },
"detail": {
"items": [
{ "title": "Track an order", "identifier": "track" },
{ "title": "Start a return", "identifier": "return" }
],
"selectedIndex": 0,
"selectedIdentifier": "track"
}
}
}
}{
"id": "evt_018f7b2d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"type": "message.received",
"api_version": "2026-09-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2d-6f7a-7b8c-9d0e-2f3a4b5c6d7e",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "Track an order",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "quick_reply",
"request_identifier": "3c2b1a09-8f7e-4d6c-9b5a-4e3d2c1b0a9f",
"chosen": ["Track an order"],
"selected": { "id": "track", "index": 0, "title": "Track an order" },
"detail": {
"items": [
{ "title": "Track an order", "identifier": "track" },
{ "title": "Start a return", "identifier": "return" }
],
"selectedIndex": 0,
"selectedIdentifier": "track"
}
}
}
}List picker
Apple sends the whole list back: the picked rows in a first section titled "You Selected" (in the customer's language) and every other row under "Others". selected reads that for you:
{
"id": "evt_018f7b2e-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"type": "message.received",
"api_version": "2026-10-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2e-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "Oak dining table",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "list_picker",
"request_identifier": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
"chosen": ["Oak dining table"],
"selected": { "id": "sku_oak_table", "title": "Oak dining table" },
"detail": {
"sections": [
{
"order": 0,
"title": "You Selected",
"multipleSelection": false,
"items": [
{
"order": 0,
"style": "default",
"title": "Oak dining table",
"subtitle": "Seats 6 · $1,240",
"identifier": "sku_oak_table"
}
]
},
{
"order": 1,
"title": "Others",
"multipleSelection": false,
"items": [
{
"order": 0,
"style": "default",
"title": "Walnut sideboard",
"subtitle": "3 drawers · $890",
"identifier": "sku_walnut_sideboard"
},
{
"order": 1,
"style": "default",
"title": "Linen armchair",
"subtitle": "4 colors · $560",
"identifier": "sku_linen_chair"
}
]
}
]
}
}
}
}{
"id": "evt_018f7b2e-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"type": "message.received",
"api_version": "2026-09-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2e-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "Oak dining table",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "list_picker",
"request_identifier": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
"chosen": ["Oak dining table"],
"selected": { "id": "sku_oak_table", "title": "Oak dining table" },
"detail": {
"sections": [
{
"order": 0,
"title": "You Selected",
"multipleSelection": false,
"items": [
{
"order": 0,
"style": "default",
"title": "Oak dining table",
"subtitle": "Seats 6 · $1,240",
"identifier": "sku_oak_table"
}
]
},
{
"order": 1,
"title": "Others",
"multipleSelection": false,
"items": [
{
"order": 0,
"style": "default",
"title": "Walnut sideboard",
"subtitle": "3 drawers · $890",
"identifier": "sku_walnut_sideboard"
},
{
"order": 1,
"style": "default",
"title": "Linen armchair",
"subtitle": "4 colors · $560",
"identifier": "sku_linen_chair"
}
]
}
]
}
}
}
}When a section sets multipleSelection and the customer picks several rows, selections replaces selected. Apple reports multipleSelection: false in detail on every reply, so read selections rather than that flag:
{
"id": "evt_018f7b2e-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
"type": "message.received",
"api_version": "2026-10-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2e-4d5e-7f6a-8b7c-8d9e0f1a2b3c",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "Oak dining table, Linen armchair",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "list_picker",
"request_identifier": "6e5d4c3b-2a1f-4e0d-9c8b-7a6f5e4d3c2b",
"chosen": ["Oak dining table", "Linen armchair"],
"selections": [
{ "id": "sku_oak_table", "title": "Oak dining table" },
{ "id": "sku_linen_chair", "title": "Linen armchair" }
],
"detail": {
"sections": [
{
"order": 0,
"title": "You Selected",
"multipleSelection": false,
"items": [
{
"order": 0,
"style": "default",
"title": "Oak dining table",
"subtitle": "Seats 6 · $1,240",
"identifier": "sku_oak_table"
},
{
"order": 1,
"style": "default",
"title": "Linen armchair",
"subtitle": "4 colors · $560",
"identifier": "sku_linen_chair"
}
]
},
{
"order": 1,
"title": "Others",
"multipleSelection": false,
"items": [
{
"order": 0,
"style": "default",
"title": "Walnut sideboard",
"subtitle": "3 drawers · $890",
"identifier": "sku_walnut_sideboard"
}
]
}
]
}
}
}
}{
"id": "evt_018f7b2e-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
"type": "message.received",
"api_version": "2026-09-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2e-4d5e-7f6a-8b7c-8d9e0f1a2b3c",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "Oak dining table, Linen armchair",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "list_picker",
"request_identifier": "6e5d4c3b-2a1f-4e0d-9c8b-7a6f5e4d3c2b",
"chosen": ["Oak dining table", "Linen armchair"],
"selections": [
{ "id": "sku_oak_table", "title": "Oak dining table" },
{ "id": "sku_linen_chair", "title": "Linen armchair" }
],
"detail": {
"sections": [
{
"order": 0,
"title": "You Selected",
"multipleSelection": false,
"items": [
{
"order": 0,
"style": "default",
"title": "Oak dining table",
"subtitle": "Seats 6 · $1,240",
"identifier": "sku_oak_table"
},
{
"order": 1,
"style": "default",
"title": "Linen armchair",
"subtitle": "4 colors · $560",
"identifier": "sku_linen_chair"
}
]
},
{
"order": 1,
"title": "Others",
"multipleSelection": false,
"items": [
{
"order": 0,
"style": "default",
"title": "Walnut sideboard",
"subtitle": "3 drawers · $890",
"identifier": "sku_walnut_sideboard"
}
]
}
]
}
}
}
}Time picker
chosen and selected.title are the slot's start time in Apple's format (2026-07-21T17:00+0000). duration is in seconds:
{
"id": "evt_018f7b2e-5e6f-7a7b-8c8d-9e0f1a2b3c4d",
"type": "message.received",
"api_version": "2026-10-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2e-6f7a-7b8c-9d0e-0f1a2b3c4d5e",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "2026-07-21T17:00+0000",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "time_picker",
"request_identifier": "7f6e5d4c-3b2a-4f1e-8d0c-9b8a7f6e5d4c",
"chosen": ["2026-07-21T17:00+0000"],
"selected": { "id": "slot-2", "title": "2026-07-21T17:00+0000" },
"detail": {
"identifier": "design-consult",
"title": "Design consultation",
"timeslots": [
{ "identifier": "slot-2", "startTime": "2026-07-21T17:00+0000", "duration": 1800 }
]
}
}
}
}{
"id": "evt_018f7b2e-5e6f-7a7b-8c8d-9e0f1a2b3c4d",
"type": "message.received",
"api_version": "2026-09-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2e-6f7a-7b8c-9d0e-0f1a2b3c4d5e",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "2026-07-21T17:00+0000",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "time_picker",
"request_identifier": "7f6e5d4c-3b2a-4f1e-8d0c-9b8a7f6e5d4c",
"chosen": ["2026-07-21T17:00+0000"],
"selected": { "id": "slot-2", "title": "2026-07-21T17:00+0000" },
"detail": {
"identifier": "design-consult",
"title": "Design consultation",
"timeslots": [
{ "identifier": "slot-2", "startTime": "2026-07-21T17:00+0000", "duration": 1800 }
]
}
}
}
}Form
responses has one entry per page the customer answered; pages left blank are skipped. title is the answer as the customer saw it and value is the raw value, such as an ISO timestamp for a date. type is the control that collected it: input, select, picker, or datePicker:
{
"id": "evt_018f7b2e-7a8b-7c9d-8e0f-1a2b3c4d5e6f",
"type": "message.received",
"api_version": "2026-10-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2e-8b9c-7d0e-9f1a-2b3c4d5e6f7a",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "J. Appleseed, Delivery, 07/30/2026",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "form",
"request_identifier": "booking-42",
"chosen": ["J. Appleseed", "Delivery", "07/30/2026"],
"responses": [
{
"page_identifier": "details",
"question": "Your details",
"answers": [
{
"identifier": "name",
"type": "input",
"title": "J. Appleseed",
"value": "J. Appleseed"
}
]
},
{
"page_identifier": "method",
"question": "Pickup or delivery?",
"answers": [
{ "identifier": "delivery", "type": "select", "title": "Delivery", "value": "delivery" }
]
},
{
"page_identifier": "when",
"question": "Preferred date",
"answers": [
{
"identifier": "date",
"type": "datePicker",
"title": "07/30/2026",
"value": "2026-07-30T07:00:00Z"
}
]
}
],
"detail": {
"version": "1.2",
"template": "messageForms",
"selections": [
{
"pageIdentifier": "details",
"title": "Your details",
"subtitle": "",
"items": [
{
"identifier": "name",
"type": "input",
"title": "J. Appleseed",
"value": "J. Appleseed"
}
]
},
{
"pageIdentifier": "method",
"title": "Pickup or delivery?",
"subtitle": "",
"items": [
{
"identifier": "delivery",
"type": "select",
"title": "Delivery",
"value": "delivery"
}
]
},
{
"pageIdentifier": "when",
"title": "Preferred date",
"subtitle": "",
"items": [
{
"identifier": "date",
"type": "datePicker",
"title": "07/30/2026",
"value": "2026-07-30T07:00:00Z"
}
]
}
]
}
}
}
}{
"id": "evt_018f7b2e-7a8b-7c9d-8e0f-1a2b3c4d5e6f",
"type": "message.received",
"api_version": "2026-09-01",
"created_at": 1718000000000,
"organization_id": "org_abc123",
"data": {
"message_id": "msg_018f7b2e-8b9c-7d0e-9f1a-2b3c4d5e6f7a",
"chat_id": "chat_018f7b2d-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"channel_id": "ch_018f7b2d-0f1e-7d2c-9b3a-4c5d6e7f8a9b",
"channel_type": "amb",
"kind": "received",
"direction": "inbound",
"text": "J. Appleseed, Delivery, 07/30/2026",
"status": "received",
"protocol": "amb",
"message_type": "interactive",
"reply_to_message_id": null,
"sender": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"recipient": "Driftwood Home",
"contact": {
"contact_id": "ct_018f7b2d-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"identifier": "urn:mbid:AQAAY0vHZ7w2kQnB4xT1Ld9sXq8RmPzEoFhUj3yNcVt6aWb5",
"name": null
},
"group": null,
"channel_address": "Driftwood Home",
"attachments": [],
"locale": "en_US",
"interactive": {
"kind": "form",
"request_identifier": "booking-42",
"chosen": ["J. Appleseed", "Delivery", "07/30/2026"],
"responses": [
{
"page_identifier": "details",
"question": "Your details",
"answers": [
{
"identifier": "name",
"type": "input",
"title": "J. Appleseed",
"value": "J. Appleseed"
}
]
},
{
"page_identifier": "method",
"question": "Pickup or delivery?",
"answers": [
{ "identifier": "delivery", "type": "select", "title": "Delivery", "value": "delivery" }
]
},
{
"page_identifier": "when",
"question": "Preferred date",
"answers": [
{
"identifier": "date",
"type": "datePicker",
"title": "07/30/2026",
"value": "2026-07-30T07:00:00Z"
}
]
}
],
"detail": {
"version": "1.2",
"template": "messageForms",
"selections": [
{
"pageIdentifier": "details",
"title": "Your details",
"subtitle": "",
"items": [
{
"identifier": "name",
"type": "input",
"title": "J. Appleseed",
"value": "J. Appleseed"
}
]
},
{
"pageIdentifier": "method",
"title": "Pickup or delivery?",
"subtitle": "",
"items": [
{
"identifier": "delivery",
"type": "select",
"title": "Delivery",
"value": "delivery"
}
]
},
{
"pageIdentifier": "when",
"title": "Preferred date",
"subtitle": "",
"items": [
{
"identifier": "date",
"type": "datePicker",
"title": "07/30/2026",
"value": "2026-07-30T07:00:00Z"
}
]
}
]
}
}
}
}Outcomes that arrive as other events
- Apple Pay. The Paid bubble in the thread doesn't send a webhook. The payment arrives as
payment.received, thenpayment.completed; see Apple Pay. - Sign in. The result arrives as
identity.verified; see Sign in. - Business invitations. Accepting arrives as a
message.receivedwithkind: "invitation", and declining closes the chat withchat.closed; see Business invitations.
Note Large replies (common for multi-page forms and list pickers with images) arrive from Apple as a reference that Blooio resolves after acknowledging the inbound. The message
textandinteractiveare backfilled once resolved, so the webhook fires with the complete reply.
Capabilities
A device advertises which interactive features it supports. GET /chats/{chatId} returns them as capabilities.amb_capability_list (for example ["QUICK", "LIST", "TIME", "FORM", "AUTH2"]) and, more usefully, as supported_interactive_kinds: the kind values you can send to this customer right now. apple_pay and imessage_app are always listed, and every kind is listed until the customer's device has reported its capabilities. Sending a kind the device can't render fails with 422 amb_capability_unavailable, unless you set fallback to downgrade it; see the fallback decision tree.
Structured output for AI agents
If you generate interactive messages with an LLM, constrain the model to the JSON Schema below (e.g. OpenAI response_format: { type: "json_schema" }, or a tool/function parameter schema) so it always emits a valid interactive object. Put the generated object under the top-level interactive field of POST /messages alongside scalar from and to:
{ "from": "support", "to": "opaque-amb-customer-id", "interactive": { "kind": "quick_reply", "...": "..." } }{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "AppleMessagesInteractive",
"description": "A Blooio Apple Messages for Business interactive object (the value of the top-level `interactive` send field). Choose exactly one kind and fill only that kind's fields.",
"type": "object",
"oneOf": [
{
"title": "quick_reply",
"type": "object",
"required": ["kind", "summary_text", "items"],
"additionalProperties": false,
"properties": {
"kind": { "const": "quick_reply" },
"summary_text": { "type": "string", "description": "Prompt shown above the options." },
"items": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"items": {
"type": "object",
"required": ["id", "title"],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "description": "Stable option id returned on the reply." },
"title": { "type": "string", "description": "Short tappable label." }
}
}
}
}
},
{
"title": "list_picker",
"type": "object",
"required": ["kind", "sections"],
"additionalProperties": false,
"properties": {
"kind": { "const": "list_picker" },
"received_message": { "$ref": "#/$defs/bubble" },
"reply_message": { "$ref": "#/$defs/bubble" },
"sections": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["title", "items"],
"additionalProperties": false,
"properties": {
"title": { "type": "string" },
"multipleSelection": { "type": "boolean" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["identifier", "title"],
"additionalProperties": false,
"properties": {
"identifier": { "type": "string" },
"title": { "type": "string" },
"subtitle": { "type": "string" }
}
}
}
}
}
}
}
},
{
"title": "time_picker",
"type": "object",
"required": ["kind", "event"],
"additionalProperties": false,
"properties": {
"kind": { "const": "time_picker" },
"received_message": { "$ref": "#/$defs/bubble" },
"reply_message": { "$ref": "#/$defs/bubble" },
"event": {
"type": "object",
"required": ["title", "timeslots"],
"additionalProperties": false,
"properties": {
"title": { "type": "string" },
"location": {
"type": "object",
"additionalProperties": false,
"properties": { "title": { "type": "string" } }
},
"timeslots": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["startTime", "duration"],
"additionalProperties": false,
"properties": {
"startTime": { "type": "string", "description": "ISO-8601, e.g. 2026-07-20T17:00:00Z." },
"duration": { "type": "integer", "description": "Slot length in seconds." }
}
}
}
}
}
}
},
{
"title": "form",
"type": "object",
"required": ["kind", "dynamic"],
"additionalProperties": false,
"properties": {
"kind": { "const": "form" },
"received_message": { "$ref": "#/$defs/bubble" },
"reply_message": { "$ref": "#/$defs/bubble" },
"dynamic": {
"type": "object",
"description": "Apple dynamic interactive message, forwarded verbatim. Uses Apple's field names.",
"required": ["startPageIdentifier", "pages"],
"additionalProperties": true,
"properties": {
"startPageIdentifier": { "type": "string" },
"showSummary": { "type": "boolean" },
"pages": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["pageIdentifier", "title"],
"additionalProperties": true,
"properties": {
"pageIdentifier": { "type": "string" },
"title": { "type": "string" },
"subtitle": { "type": "string" },
"nextPageIdentifier": { "type": "string" },
"textInputs": {
"type": "array",
"items": {
"type": "object",
"required": ["identifier", "title"],
"additionalProperties": true,
"properties": {
"identifier": { "type": "string" },
"title": { "type": "string" },
"type": { "type": "string", "description": "Apple input type, e.g. text, name, email, phone." }
}
}
},
"datePicker": {
"type": "object",
"required": ["identifier", "title"],
"additionalProperties": true,
"properties": {
"identifier": { "type": "string" },
"title": { "type": "string" }
}
}
}
}
}
}
}
}
}
],
"$defs": {
"bubble": {
"type": "object",
"additionalProperties": false,
"properties": {
"title": { "type": "string" },
"subtitle": { "type": "string" },
"style": { "type": "string", "enum": ["icon", "small", "large"] }
}
}
}
}Guidance to put in your system prompt:
- Choose exactly one
kindand populate only that kind's fields. quick_replymust not includereceived_message/reply_message— send a plain text bubble first.time_pickerstartTimeis ISO-8601 anddurationis in seconds.- Keep option, row, and section titles short — they render as tappable UI.
Note OpenAI's strict structured outputs don't allow a top-level
oneOf. If you use strict mode, pass a single branch (onekind) from the schema above as yourjson_schema, or use one tool per kind. The same schema is served machine-readable in the reference as theinteractivefield of the send request inopenapi.v4.json.