Chat UI from Actions
Return cards, confirmations, order tracking, products, quick replies and charts from a custom action, and the web chat shows them under the bot's reply.
A custom action can return more than data for the bot. It can also return UI for the customer: a card with a subscription's details, a "Cancel order #1042?" confirmation with two buttons, a shipment's tracking, a few products. The web chat draws it under the bot's reply, the way an app would, instead of leaving the bot to describe it in prose.
Where it shows
- Only in web chat with the new chat experience on ("New chat experience" in the web chat deployment settings). Email, phone, other messengers and the classic widget never get it. Octocom drops it for them, so the same action works on every channel without checking which one it runs on.
- The bot never sees it. It reads the rest of your result as usual. Keep returning the facts it needs to answer: on every other channel, the bot's answer is all the customer gets.
- Its colors follow the chat. Cards use the bot message colors and their buttons the customer message colors, unless you set Cards & Buttons colors in the web chat settings.
Returning UI
Add an octocom_ui key to the object your action returns, holding one item or a list of up to 6 items. Every item has a type and the fields listed under Item reference.
Python action:
def execute_action(context):
sub = fetch_subscription(context["args"]["email"])
return {
"subscription": sub, # what the bot reads
"octocom_ui": [ # what the customer sees
{
"type": "card",
"title": sub["plan_name"],
"subtitle": "Active subscription",
"fields": [
{"label": "Next delivery", "value": sub["next_date"]},
{"label": "Price", "value": f"{sub['price']} EUR / month"},
],
"buttons": [
{"label": "Manage", "url": sub["portal_url"]},
{"label": "Skip next delivery", "message": "Skip my next delivery"},
],
}
],
}Python actions can also use the show_card and ask_confirmation helpers, which add the same items for you.
API action: the endpoint you call answers with the key at the top level of its JSON body. This is useful when the endpoint is your own:
{
"orderId": "1042",
"status": "in_transit",
"octocom_ui": {
"type": "orderTracking",
"orderId": "1042",
"courier": "DPD",
"trackingNumber": "05212345678901",
"trackingUrl": "https://tracking.dpd.de/05212345678901",
"estimatedDeliveryAt": "2026-10-09"
}
}Only a top-level octocom_ui counts. Nested keys, and actions that return a list or plain text, are left alone.
Item reference
Field names are case-sensitive. Optional fields can be left out or set to null. URLs must start with https:// or http://. Dates are ISO 8601 ("2026-10-09" or "2026-10-09T14:30:00Z").
card
A title, label/value rows and up to three buttons. Use it for anything with a few facts: a subscription, a booking, a loyalty balance, a warranty.
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | yes | Max 160 characters |
subtitle | string | no | Max 240 |
imageUrl | URL | no | Shown across the top |
fields | list | no | Up to 12 {"label": string, "value": string or number}; label max 60, value max 240 |
buttons | list | no | Up to 3. {"label", "url"} opens a link. {"label", "message"} sends message as the customer's reply. Label max 40, message max 200. The first link button is filled |
confirmation
A question with two buttons, for anything the customer should agree to before it happens. The tapped button is sent as the customer's reply, and the bot acts on that reply on its next turn. Showing a confirmation changes nothing by itself, so do the change in a separate action once the customer has confirmed.
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | yes | What will happen, e.g. "Cancel order #1042?". Max 160 |
description | string | no | Details, e.g. the refund amount. Max 400 |
confirmLabel | string | yes | e.g. "Yes, cancel it". Max 40 |
cancelLabel | string | yes | e.g. "No, keep it". Max 40 |
confirmMessage | string | no | Sent instead of confirmLabel when tapped. Max 200 |
cancelMessage | string | no | Sent instead of cancelLabel when tapped. Max 200 |
destructive | boolean | no | Red confirm button for things that can't be undone |
The buttons only work while the confirmation is the bot's latest message. Once the customer answers, it stays on screen with their choice marked.
orderTracking
A shipment's progress: status, a drawn route, arrival date, courier and tracking number, the courier's latest scans and the items.
| Field | Type | Required | Notes |
|---|---|---|---|
orderId | string/number | yes | Shown as "Order …" |
stage | string | no | ordered, shipped, outForDelivery, delivered, cancelled or problem. Left out: delivered if deliveredAt is set, shipped if there is a tracking number or shippedAt, otherwise ordered |
statusText | string | no | Your own wording, shown for cancelled and problem. Max 120 |
courier | string | no | Max 60 |
trackingNumber | string | no | Can be copied by the customer. Max 80 |
trackingUrl | URL | no | The "Track package" button |
orderUrl | URL | no | The "View order" button, used when there is no trackingUrl |
destination | string | no | City and country only, e.g. "Vilnius, LT". Never the street. Max 120 |
orderedAt | date | no | |
shippedAt | date | no | |
estimatedDeliveryAt | date | no | "Arriving …" |
deliveredAt | date | no | "Delivered …" |
events | list | no | Up to 8 {"description", "location"?, "at"?}, newest first |
items | list | no | Up to 10 {"name", "quantity"?, "imageUrl"?} |
You don't need this for the built-in order lookups (getOrderDetails…): they already show a tracking card. Use it when orders come from a system of your own.
products
Product cards with photo, name, price and a link. The built-in catalog search already shows products from your synced catalog. Use this for products from elsewhere, e.g. a bundle your API builds.
| Field | Type | Required | Notes |
|---|---|---|---|
products | list | yes | 1 to 10 products, see below |
currency | string | no | Printed next to prices, e.g. "€" or "SEK" |
Each product: name (required), id, url, imageUrl, price, compareAtPrice (the price before discount; the discount badge is worked out from it), inStock (default true), note (one line on why it fits, max 120).
quickReplies
Up to 4 tap-to-send replies under the message, e.g. {"type": "quickReplies", "options": ["Yes", "No"]}. Each option is max 40 characters and is sent as the customer's own message.
chart
A small bar or line chart.
| Field | Type | Required | Notes |
|---|---|---|---|
kind | string | no | bar (default) or line |
title | string | no | Max 120 |
labels | list | yes | 1 to 24 x-axis labels |
series | list | yes | 1 to 4 {"name", "values"}, with one number per label |
unit | string | no | Appended to values, e.g. "cm". Max 12 |