Functions and states
Widget functions respond to visitor selections, and states control which sections appear. Reference function settings, placeholders, saved variables, and condition operators.
A function is a named behavior that runs when a visitor selects a button or list item. A state is a named section that appears when its visibility rules allow it. Configure both in the widget builder.
Function types
In Functions, select Add your first function, or Add function when functions already exist. Each function needs a unique Function name and a Function type. Names match ^[A-Za-z_][A-Za-z0-9_]{0,63}$, such as pickProduct.
| Function type | Setting | Description |
|---|---|---|
| Open link | URL | Opens an HTTP or HTTPS URL in a new tab. Example: https://example.com/orders/{{order.number}}. Other URL schemes are refused. |
| Send message | Message | Sends text into the conversation as the visitor. Example: Tell me about {{productName}}. Empty messages after substitution and trimming do not send. In the turn it starts, the action that showed the widget does not run again, so a button such as Book 9:30 AM cannot reopen the same card. |
| Set variables | Variables | Updates local widget variables. Example: key pickedName, value {{productName}}. |
| Dismiss widget | No extra setting | Hides this widget. |
| Call API | API connection and follow-ups | Sends a request through a saved API connection and updates widget state using the response. |
Functions do not run while the conversation is busy.
Call API
Call API functions send HTTP requests from Chatvox to your backend through a saved API connection when a visitor selects a button or list item. They cost no credits and do not invoke an LLM. Only one Call API function can run at a time per reply widget.
Configuration
A Call API function includes:
- Function name: unique identifier matching
^[A-Za-z_][A-Za-z0-9_]{0,63}$. - Additional inputs: up to 10 keys passed when the button is clicked (such as
slot). - API connection: choose a saved connection for this agent, or create one from the function editor. It supplies the base URL, authentication, and headers.
- Method:
GET,POST,PUT,PATCH, orDELETE. - Path and query: the endpoint path, such as
/bookings/{{order_id}}. The request uses the connection’s base URL. The base URL and final request must use HTTPS and reach a public address. - Send fields as: JSON body or Query params.
GETrequests always send declared inputs as query parameters. - JSON body (optional): a JSON template for methods other than
GET. Use placeholders such as{{appointmentSlot}}to include widget data or input values. When you set a template, it defines the JSON body; Chatvox does not add declared inputs to it automatically. If Send fields as is Query params, declared inputs still go in the URL when you set a JSON body. - Return schema: up to 20 return fields mapping a field key to a dot path in the JSON response (for example,
bookingRef←data.reference). - Success path (optional): dot path in the JSON response that must contain a value Chatvox parses as true, such as
true,1, or"yes", for the call to count as a success. If omitted, any 2xx HTTP response status is considered successful. - Timeout (seconds): request timeout from 1 to 15 seconds (default 8 seconds).
- Loading: visual feedback while the request runs:
- None: buttons are disabled only.
- Button: the clicked
ButtonorListViewItemshows an inline spinner. - Whole widget: a semi-transparent overlay with a spinner covers the card.
Tokens and precedence
The path, connection header values, and request body support double-brace tokens such as {{date}}. Values resolve with the following precedence (highest precedence wins):
- Built-ins:
conversation.id,contact.*(verified contact details),page.url, andutm.*. These values come from the conversation; contact values are available for verified contacts. - Declared inputs: values passed in
additionalInputsmatching declared input keys. - Variables: current visitor widget variables (including values from form inputs).
- Data: the widget’s initial reply data from message metadata.
Tokens in the path are URL-encoded. JSON body templates are decoded and re-encoded, preserving a value’s type when it consists of one placeholder.
Follow-ups
Call API functions use lifecycle follow-ups instead of a single After it runs setting:
- On start: runs locally as soon as the visitor clicks (None, Dismiss widget, or Set variables).
- On success: runs when the request returns a 2xx status and, if defined, its Success path parses as true. Values in Set variables can read returned fields, such as
{{bookingRef}}. - On failure: runs when the request fails, times out, or returns a non-success response (None, Dismiss widget, or Set variables). If On failure is set to None, an inline alert displays:
Couldn’t complete that. Try again.
State changes are saved to the server in a single save after the request settles. If a visitor reloads during an in-flight request, the card restores to its pre-click state.
Testing in the builder
The widget builder provides two tools for Call API functions. Both send a real request to your API:
- Run: in the function editor, enter test values for declared inputs and select Run. Chatvox sends a test request and displays the status badge, response time, extracted return fields, and raw JSON response.
- Populate from API: select Populate from API to run the request and inspect leaf paths in the JSON response. Selecting paths adds them directly to your return schema without typing.
- Preview result toggle: in the preview pane, use the Call API result: Success | Failure toggle to simulate both success and failure flows without calling your backend.
Follow-ups for other functions
After it runs chooses one follow-up:
| Setting | Description |
|---|---|
| None | No further change. |
| Dismiss widget | Hides the widget after the main behavior. |
| Set variables | Applies Variables after the action using the same value rules as Set variables. |
For example, a Send message function with Message set to I choose {{productName}} and After it runs set to Dismiss widget sends the selection and hides the card. An invalid link or empty message stops before its follow-up. Follow-ups cannot call another named function.
Follow-up placeholders use the original click scope. They do not read variables newly assigned by the main function during that same click.
Call a function from code
Use onClickAction on a Button or ListViewItem. Its functionName must be a literal string naming a function you defined. additionalInputs is an optional object of values for that click.
<Card>
<Button
label="Choose this product"
onClickAction={{
functionName: "pickProduct",
additionalInputs: { productName: product.name },
}}
/>
</Card>
Declare product.name as a widget field. Define pickProduct in Functions, for example as Send message with Message set to I choose {{productName}}.
In a loop, additional inputs can read the current item:
<Card>
<ListView>
{products.map((product) => (
<ListViewItem
onClickAction={{
functionName: "pickProduct",
additionalInputs: { productName: product.name },
}}
>
<Text value={product.name} />
</ListViewItem>
))}
</ListView>
</Card>
Declare products as a List field with visitor-safe items. Click inputs are not saved automatically. Use Set variables to retain a selected value.
Placeholders
Open link URLs, messages, and Set variables values support double-brace placeholders such as {{order.number}}. These templates are separate from JSX expressions such as {order.number} in code.
| Source | Example |
|---|---|
| Widget data | {{order.number}} |
| Saved widget variables | {{pickedName}} |
| Current loop item and optional index | {{product.name}}, {{index}} when those map parameters exist |
| Additional inputs for the click | {{productName}} |
Variables override widget data with the same key. Loop parameters override matching keys inside their loop. Additional inputs override matching keys for the function call.
Paths contain letters, digits, underscores, and dots. Use {{products.0.name}} for a numeric segment. Bracket notation such as {{products[0].name}} is not supported in placeholders. Prototype-related path segments are blocked. Open link, message, and Set variables placeholders read widget data, saved variables, loop values, and click inputs. Call API functions can also use the built-in conversation values listed above; they do not expose API credentials to the widget.
In Open link URLs and messages, values become text. Missing or null values become empty text. Open link values are not URL-encoded, so use data that is safe for the destination URL. For example, with {"order":{"number":"A-1234"}}, Order {{order.number}} becomes Order A-1234.
Set variables rules
| Setting | Rule |
|---|---|
| Variable name | Matches ^[A-Za-z_][A-Za-z0-9_]{0,63}$. Dot paths are not allowed. |
| Reserved names | __proto__, prototype, and constructor are refused. |
| Variable value | A text template of up to 1,000 characters, or null. |
| Accepted result | Text, a finite number, a boolean, or null. Objects, lists, missing values, and non-finite numbers are not assigned. |
A value containing only one placeholder preserves its underlying type. For example, {{quantity}} preserves a numeric quantity, while Quantity: {{quantity}} produces text. Entering true directly in the value input produces the text "true", not a boolean. A null template assigns null; a missing single-placeholder value leaves the variable unchanged.
Assignments use the original click values. An earlier assignment does not feed a later assignment in the same call.
Variables and dismissal are saved with the widget on its specific reply and restored when the conversation reloads. They do not change another reply’s card or the widget definition. Sent messages remain visitor messages. The reply retains its original widget definition and data after action or widget edits.
State tags
In States, select Add your first state, or Add state when states already exist. Each State name must be unique and match ^[A-Z][A-Za-z0-9]{0,39}$. Use a distinct name such as Picked, then wrap components in a tag with that exact name. State tags have no props.
<Card>
<Picked>
<Text value="You have selected a product." />
</Picked>
</Card>
| Visibility | Behavior |
|---|---|
| Always visible | Shows the children regardless of conditions. |
| Off | Turns the state off without deleting it. The children never show, and the conditions stay saved. |
| Conditional | Shows the children only when condition groups match. No groups, or a group with no conditions, evaluates to false. |
Conditions read widget data and variables by dot path, such as order.status or pickedName. Variables override data fields with the same key. Inside loops, states can also read the current item’s scope.
Condition operators
Under Show when, enter a Condition field, select a Condition operator, and enter a comparison value when needed. Comparison values are text. Matches are case-sensitive.
| Operator | Exact behavior |
|---|---|
| Equals | Converts the field value to text and compares it with the expected text. Missing and null become empty text. The number 3 equals the text 3. |
| Does not equal | The inverse of Equals. |
| Contains | For text, checks for a substring. For a list, checks whether any item converted to text equals the expected text, treating null as empty text. Other types do not match. |
| Does not contain | The inverse of Contains, including true for unsupported field types. |
| Greater than | Converts both values to numbers and checks a strict greater-than comparison. |
| Less than | Converts both values to numbers and checks a strict less-than comparison. |
| Is empty | Matches missing values, null, empty text, an empty list, or an object with no keys. Zero, false, and whitespace-only text are not empty. |
| Is not empty | The inverse of Is empty. |
Numeric conversion follows JavaScript Number: empty text and null become zero, booleans become zero or one, and missing or nonnumeric text becomes NaN. Comparisons involving NaN are false. Contains with empty expected text matches every text value.
For example, order.status Equals Shipped matches "Shipped" but not "shipped". tags Contains sale matches ["sale", "new"], but not ["wholesale"].
Conditions and groups
And requires both the result so far and the next condition to match. Or requires either to match. Conditions combine left to right, without normal expression precedence. Groups evaluate separately, then their results also combine left to right. The first condition and first group do not need a connector.
For example, A Or B And C means (A Or B) And C. To express A Or (B And C), put A in one group and B And C in a second group. Use Or as the second group’s connector.
Combined example: show a picked state
Declare product.name as a Text field and use this default data:
{
"product": { "name": "Canvas bag" }
}
Configure the function:
| Setting | Value |
|---|---|
| Function name | pickProduct |
| Function type | Set variables |
| Variable name | pickedName |
| Variable value | {{productName}} |
| After it runs | None |
Configure the state:
| Setting | Value |
|---|---|
| State name | Picked |
| Visibility | Conditional |
| Condition field | pickedName |
| Condition operator | Is not empty |
Use this code:
<Card>
<Title value={product.name} />
<Button
label="Choose product"
onClickAction={{
functionName: "pickProduct",
additionalInputs: { productName: product.name },
}}
/>
<Picked>
<Text value={`You picked ${pickedName}`} />
</Picked>
</Card>
Before selection, pickedName is missing and Picked stays hidden. Selecting Choose product saves Canvas bag to pickedName, so the state appears. The selection survives a conversation reload. In the builder preview, Reset clears it.
Walkthrough: Book an appointment without a second action
This walkthrough converts the Appointment slots template from sending a chat message to booking directly through an API using a Call API function.
Create the widget from the template
Go to Build > Widgets and select Create widget. In the template gallery, select Appointment slots, then select Use template.
Go to Fields and add two fields:
| Field | Type | Description |
|---|---|---|
status |
Text | Booking status (booking, booked, or error). |
bookingRef |
Text | Confirmation reference returned by the API. |
In Default, update the JSON example to include initial empty values:
{
"date": "Monday, October 19",
"slots": [
{ "label": "9:30 AM" },
{ "label": "11:00 AM" },
{ "label": "1:30 PM" },
{ "label": "3:00 PM" }
],
"picked": "",
"status": "",
"bookingRef": ""
}Define states for booked and failed views
Go to States and add two conditional states:
| State name | Visibility | Condition field | Operator | Value |
|---|---|---|---|---|
Booked |
Conditional | status |
Equals | booked |
BookingFailed |
Conditional | status |
Equals | error |
Configure the Call API function
Go to Build > API connections and create a connection named Bookings with base URL https://api.example.com. Then return to Functions and open bookSlot. Change its Function type from Send message to Call API, and select the Bookings connection.
Configure the function settings:
- Method:
POST - Path and query:
/bookings - Request body (optional):
{
"date": "{{date}}",
"time": "{{picked}}",
"conversation_id": "{{conversation.id}}",
"customer_email": "{{contact.email}}"
}-
Return schema: add a row with field key
bookingRefand response pathdata.reference. -
Loading: select Button.
-
On start: select Set variables, with variable
statusset tobooking. -
On success: select Set variables, with variable
statusset tobooked, and variablebookingRefset to{{bookingRef}}. -
On failure: select Set variables, with variable
statusset toerror.
Go to Code and update the markup so the card handles the booking states:
<Card size="md" status={{ text: "Book a call", icon: "calendar" }}>
<Col gap={1}>
<Title value="Pick a time" size="md" />
<Caption value={date} />
</Col>
<Row gap={2} justify="between" wrap={true}>
{slots.map((slot) => (
<Col width="48%">
<Button
label={slot.label}
variant={picked === slot.label ? "solid" : "outline"}
size="md"
block={true}
iconStart={picked === slot.label ? "check" : ""}
onClickAction={{ functionName: "pickSlot", additionalInputs: { slot: slot.label } }}
/>
</Col>
))}
</Row>
<Picked>
<Divider />
<Row gap={2} align="center">
<Icon name="check-circle" color="success" />
<Text value={`${date} at ${picked}`} weight="medium" />
</Row>
<Button
label="Confirm booking"
variant="solid"
size="md"
block={true}
iconStart="calendar"
onClickAction={{ functionName: "bookSlot" }}
/>
</Picked>
<Booked>
<Divider />
<Box border={true} radius="lg" padding={3} gap={2} align="center">
<Icon name="check-circle" color="success" size={24} />
<Text value={`Booked for ${date} at ${picked}`} weight="semibold" />
<Caption value={`Reference: ${bookingRef}`} />
</Box>
</Booked>
<BookingFailed>
<Divider />
<Box border="error" radius="lg" padding={3} gap={1} align="center">
<Icon name="triangle-alert" color="error" />
<Text value="Could not complete booking. Please try another time." />
</Box>
</BookingFailed>
</Card>In the preview header, toggle Call API result between Success and Failure to verify both outcomes. Save the widget.