# Cards from Action Data
The card source Action Data renders a card slider directly from the response body of a webhook action. Every element of a list in the response becomes one card. A card template, written once with the regular template syntax, maps the fields of each element to the card's image, title, description and buttons.
The language model only has to call the action and pick the template. Building the cards is deterministic: no values are rewritten, shortened or mixed up along the way.
Setting it up in the Hub
Step-by-step instructions for the Hub UI are in the Help Center: Karten-Slider aus Action-Daten erstellen.
# Use case
A typical scenario is a product search: a user asks "Welche Waschmaschinen habt ihr unter 700 €?", the AI Agent calls an AI Action, and your shop backend returns a list of matching products with name, price, image and link.
With the card source AI Generated, the language model reads that response and writes every card itself. This works, but it gets noticeably slower with many elements, and the model may rephrase, shorten or swap values that were already correct.
With Action Data, the response is mapped to cards as-is. Choose the card source based on your data:
| Use | When |
|---|---|
| Action Data | The response is already structured and the values are display-ready. |
| AI Generated | The data is unstructured, or the model should summarize, select or rewrite content. |
A form message with {{#each}} | You need a plain list or a set of links rather than a card slider. Only available in forms; in an AI Agent, the model can only be instructed to answer in that format. |
# How it works
- The language model calls the AI Action and picks the response template based on its
Instruction. - The items path (
Path to items) is resolved against the response body and yields an array. - For every element of that array, up to the card limit (
Max. cards), the card template is rendered with the element as its root. - Buttons are rendered either from fixed button templates or from a list inside the element.
- The widget shows the intro text (
Answer-text) followed by the slider.
# Configuration reference
| Hub field | Resolved against | Description |
|---|---|---|
Template Name | – | Internal name of the template. |
Instruction | – | Tells the language model when to use this template. |
Answer-text | – | Intro text shown above the slider. |
Image display | – | Fill (crop) (default) or Fit (full image). Fit keeps the entire image visible, which works best for product photos. |
Image source | – | From the image URL (default) or From the linked page, see Image source. |
Card source | – | Must be set to Action Data. |
Webhook Action | – | The action whose response provides the cards. |
Max. cards | – | Maximum number of cards. Empty means no limit. |
Path to items | response root | Dot path to the array. Empty if the response itself is an array. |
Image URL, Title, Description | current element | Card templates in the Card configuration section. |
Buttons | – | Fixed buttons or From a list. |
Path to buttons | current element | Only for From a list: dot path to the button array. |
Title, Button URL (button) | current element / current list entry | Button templates. Fixed buttons resolve against the element, list buttons against the list entry. |
# Placeholder scope
This is the main difference to templates elsewhere in the platform. In a regular bot message, every path starts at the Context Name of the webhook:
In a card template, every path starts at the current element. You write the field name directly, without the Context Name and without the path to the list:
There are three reference points in total:
| What | Resolved against |
|---|---|
| Items path | The complete response body |
| Card placeholders and buttons path | The current element |
| Placeholders in list buttons | The current entry of the button list |
The rest of the syntax works as described in Templates:
- Nested fields use dot notation:
{{preis.betrag}}. - Array entries are addressed by index in square brackets, starting at
0:{{links.[0].url}}. The same syntax works in the items path and the buttons path. - Text and placeholders can be mixed freely:
Jetzt {{preis.betrag}} € statt {{preis.uvp}} €. - Whitespace inside the braces is optional.
{{ modell }}and{{modell}}are equivalent. - Field names are case-sensitive. If the field is called
Modell,{{modell}}won't match.
The Description field is a WYSIWYG editor with bold, italic, bulleted and numbered lists and links. Placeholders can be used inside formatted text.
# Items path
The items path tells the system where the list of elements is located in the response.
| Response shape | Items path |
|---|---|
[ {…}, {…} ] | (empty) |
{ "kurse": [ … ] } | kurse |
{ "ergebnis": { "produkte": [ … ] } } | ergebnis.produkte |
{ "seiten": [ { "produkte": [ … ] } ] } | seiten.[0].produkte |
Everything outside the resolved array (status fields, metadata, hit counts) is ignored. If the array is empty, neither the slider nor the Answer-text is shown. Instead, the language model replies in its own words, e.g. that no washing machines are currently in stock.
Silent failure
If the items path doesn't resolve to an array, the template is silently skipped: no slider is rendered, and the user doesn't see an error message. Whenever cards don't show up, test the action in the Playground and check that the response format matches the items path.
# Buttons
Fixed buttons appear on every card. Their title and URL templates are resolved against the current element. Use them when every element gets the same buttons, e.g. always "Zum Produkt".
Buttons from a list (From a list) create one button per entry of an array inside the element. Use them when each element carries its own list of links, and the number of links may differ.
Path to buttonsis relative to the element, e.g.linksorangebote.haendler, never a path from the response root.- The button
TitleandButton URLtemplates are resolved against the list entry, e.g.{{label}}and{{url}}.
# Image source
From the image URL(default): the image URL comes from theImage URLtemplate, i.e. from the action data.From the linked page: the page'sog:imageis loaded automatically from the page the card links to. The first button with a URL determines the page; for buttons from a list, that's the first list entry. Useful when your API doesn't return image URLs.
# Examples
All examples use a fictional shop selling washing machines.
# Example 1: Top-level array, fixed buttons
The response is an array. Every product gets the same two buttons.
Response body
[
{
"artikelnummer": "WM-8014",
"marke": "Nordwerk",
"modell": "AquaPro 8",
"kapazitaet_kg": 8,
"drehzahl": 1400,
"energieklasse": "A",
"preis": "549,00",
"bild": "https://shop.example.de/img/wm-8014.jpg",
"url": "https://shop.example.de/p/wm-8014"
},
{
"artikelnummer": "WM-9216",
"marke": "Weißhaus",
"modell": "SilentCare 9",
"kapazitaet_kg": 9,
"drehzahl": 1600,
"energieklasse": "A",
"preis": "679,00",
"bild": "https://shop.example.de/img/wm-9216.jpg",
"url": "https://shop.example.de/p/wm-9216"
}
]
Configuration
| Field | Value |
|---|---|
Path to items | (empty) |
Image URL | {{bild}} |
Title | {{marke}} {{modell}} |
Description | {{kapazitaet_kg}} kg, {{drehzahl}} U/min, Energieklasse {{energieklasse}}. Preis: {{preis}} € |
Buttons | Fixed buttons |
| Button 1 | Title: Zum Produkt, Button URL: {{url}} |
| Button 2 | Title: Verfügbarkeit prüfen, Button URL: https://shop.example.de/filialen?artikel={{artikelnummer}} |
Result
| Card | Title | Description | Buttons |
|---|---|---|---|
| 1 | Nordwerk AquaPro 8 | 8 kg, 1400 U/min, Energieklasse A. Preis: 549,00 € | Zum Produkt, Verfügbarkeit prüfen (…/filialen?artikel=WM-8014) |
| 2 | Weißhaus SilentCare 9 | 9 kg, 1600 U/min, Energieklasse A. Preis: 679,00 € | Zum Produkt, Verfügbarkeit prüfen (…/filialen?artikel=WM-9216) |
# Example 2: Top-level array, buttons from a list
Each product carries its own links. The number of links differs.
Response body
[
{
"marke": "Nordwerk",
"modell": "AquaPro 8",
"kurzinfo": "Frontlader mit 8 kg und Mengenautomatik.",
"preis": "549,00",
"bild": "https://shop.example.de/img/wm-8014.jpg",
"links": [
{ "label": "Zum Produkt", "url": "https://shop.example.de/p/wm-8014" },
{ "label": "Datenblatt (PDF)", "url": "https://shop.example.de/datenblatt/wm-8014.pdf" }
]
},
{
"marke": "Weißhaus",
"modell": "SilentCare 9",
"kurzinfo": "Besonders leiser Frontlader mit Dampfprogramm.",
"preis": "679,00",
"bild": "https://shop.example.de/img/wm-9216.jpg",
"links": [
{ "label": "Zum Produkt", "url": "https://shop.example.de/p/wm-9216" },
{ "label": "Datenblatt (PDF)", "url": "https://shop.example.de/datenblatt/wm-9216.pdf" },
{ "label": "Aufbauservice buchen", "url": "https://shop.example.de/service/aufbau?artikel=wm-9216" }
]
}
]
Configuration
| Field | Value |
|---|---|
Path to items | (empty) |
Image URL | {{bild}} |
Title | {{marke}} {{modell}} |
Description | {{kurzinfo}} Ab {{preis}} € |
Buttons | From a list |
Path to buttons | links |
Title (button) | {{label}} |
Button URL | {{url}} |
Result
| Card | Title | Description | Buttons |
|---|---|---|---|
| 1 | Nordwerk AquaPro 8 | Frontlader mit 8 kg und Mengenautomatik. Ab 549,00 € | Zum Produkt, Datenblatt (PDF) |
| 2 | Weißhaus SilentCare 9 | Besonders leiser Frontlader mit Dampfprogramm. Ab 679,00 € | Zum Produkt, Datenblatt (PDF), Aufbauservice buchen |
# Example 3: Nested response, fixed buttons
The list is nested inside the response, and the product data is nested as well.
Response body
{
"status": "ok",
"suche": { "begriff": "waschmaschine frontlader", "treffer": 2 },
"ergebnis": {
"produkte": [
{
"id": "WM-7012",
"slug": "nordwerk-ecowash-7",
"name": "EcoWash 7",
"hersteller": { "name": "Nordwerk" },
"technik": { "kapazitaet_kg": 7, "drehzahl": 1200, "energieklasse": "B" },
"preis": { "betrag": "429,00", "uvp": "499,00", "waehrung": "EUR" },
"medien": { "hauptbild": "https://shop.example.de/img/wm-7012.jpg" }
},
{
"id": "WM-1018",
"slug": "weisshaus-maxload-10",
"name": "MaxLoad 10",
"hersteller": { "name": "Weißhaus" },
"technik": { "kapazitaet_kg": 10, "drehzahl": 1400, "energieklasse": "A" },
"preis": { "betrag": "729,00", "uvp": "799,00", "waehrung": "EUR" },
"medien": { "hauptbild": "https://shop.example.de/img/wm-1018.jpg" }
}
]
}
}
Configuration
| Field | Value |
|---|---|
Path to items | ergebnis.produkte |
Image URL | {{medien.hauptbild}} |
Title | {{hersteller.name}} {{name}} |
Description | {{technik.kapazitaet_kg}} kg, Energieklasse {{technik.energieklasse}}. Jetzt {{preis.betrag}} € statt {{preis.uvp}} € |
Buttons | Fixed buttons |
| Button 1 | Title: Details ansehen, Button URL: https://shop.example.de/p/{{slug}} |
Result
| Card | Title | Description | Buttons |
|---|---|---|---|
| 1 | Nordwerk EcoWash 7 | 7 kg, Energieklasse B. Jetzt 429,00 € statt 499,00 € | Details ansehen (…/p/nordwerk-ecowash-7) |
| 2 | Weißhaus MaxLoad 10 | 10 kg, Energieklasse A. Jetzt 729,00 € statt 799,00 € | Details ansehen (…/p/weisshaus-maxload-10) |
status and suche are ignored. Cards are only created from ergebnis.produkte.
# Example 4: Nested response, buttons from a nested list
A price comparison: every washing machine has several retailer offers, and each offer becomes a button.
Response body
{
"daten": {
"waschmaschinen": [
{
"name": "Weißhaus SilentCare 9",
"bild_url": "https://vergleich.example.de/img/silentcare-9.jpg",
"kurzbeschreibung": "Besonders leiser Frontlader mit Dampfprogramm.",
"angebote": {
"anzahl": 2,
"haendler": [
{ "shop": "Elektro Meyer", "preis": "659,00", "link": "https://elektro-meyer.example.de/silentcare-9" },
{ "shop": "Haushalt Direkt", "preis": "679,00", "link": "https://haushalt-direkt.example.de/p/88213" }
]
}
},
{
"name": "Nordwerk AquaPro 8",
"bild_url": "https://vergleich.example.de/img/aquapro-8.jpg",
"kurzbeschreibung": "Frontlader mit 8 kg und Mengenautomatik.",
"angebote": {
"anzahl": 1,
"haendler": [
{ "shop": "Elektro Meyer", "preis": "539,00", "link": "https://elektro-meyer.example.de/aquapro-8" }
]
}
}
]
}
}
Configuration
| Field | Value |
|---|---|
Path to items | daten.waschmaschinen |
Image URL | {{bild_url}} |
Title | {{name}} |
Description | {{kurzbeschreibung}} {{angebote.anzahl}} Angebot(e) verfügbar. |
Buttons | From a list |
Path to buttons | angebote.haendler |
Title (button) | {{shop}}: {{preis}} € |
Button URL | {{link}} |
Result
| Card | Title | Description | Buttons |
|---|---|---|---|
| 1 | Weißhaus SilentCare 9 | Besonders leiser Frontlader mit Dampfprogramm. 2 Angebot(e) verfügbar. | Elektro Meyer: 659,00 €, Haushalt Direkt: 679,00 € |
| 2 | Nordwerk AquaPro 8 | Frontlader mit 8 kg und Mengenautomatik. 1 Angebot(e) verfügbar. | Elektro Meyer: 539,00 € |
WARNING
The buttons path is angebote.haendler, not daten.waschmaschinen.angebote.haendler. It is resolved relative to the element. Likewise, {{preis}} in the button title refers to the retailer offer, not to the washing machine.
# Designing the webhook response
If you control the API behind the action, a few decisions on your side make the card template simpler:
- Return an array of objects. A wrapper object is fine; point the items path at the array.
- Deliver display-ready values. Values are rendered exactly as they appear in the response, with no formatting applied. Return prices as formatted strings (
"549,00") rather than numbers (549, which would render as 549). - Keep field names stable. Placeholders are case-sensitive and must match the JSON keys exactly.
- Include image URLs if possible. Otherwise, use the image source
From the linked pageand make sure the first button of each card links to a page that sets anog:image. - Limit large result sets, either in your API or via
Max. cards.
# Related
- Templates: syntax basics, nested values, arrays, Markdown
- Webhooks: setting up webhooks and their Context Name
- Conversation Context
← Webhooks