Custom Actions
Connect your bot to external systems. Custom actions let workflows fetch data, create orders, process refunds, and more — using simple API calls or Python scripts.
Actions let your bot do things — not just talk. They connect workflows to your systems: ERPs, payment processors, CRMs, subscription platforms, shipping APIs.
Actions are not active by default. You must enable them inside a workflow.
How actions work
- You create an action — give it a name, description, and define how it connects to your system
- You enable it in a workflow
- In the workflow's instructions, you tell the AI when to call it (e.g., "Step 3: call
getOrderDetails") - The AI calls the action with the required arguments
- The action runs and returns data to the AI
- The AI uses the returned data in its response
Action types
There are two types: API actions for simple HTTP calls, and Python actions for anything that needs logic.
| API Action | Python Action | |
|---|---|---|
| Best for | Simple GET/POST requests | Multi-step logic, data transformation, multiple API calls |
| Setup | Configure URL, method, headers, and params in the UI | Write a Python script |
| Dynamic inputs | $variableName syntax in URL, headers, or body | context["args"]["variableName"] |
| Examples | Fetch order status, get store details, check stock | Process refunds with validation, compose data from multiple APIs, tag conversations |
API Actions
API actions make a single HTTP request to an external endpoint. They're the simplest way to connect to an API — no code required.
Name
Use camelCase and be descriptive. The AI uses the name to understand the action.
getOrderDetailscheckProductStockgetStoreLocations
Description
A one-sentence explanation of what the action does. Write it so the AI understands when to call it.
Retrieves the customer's order status, items, and tracking link given an order ID.
API configuration
| Field | Description |
|---|---|
| Base URL | The root URL of your API (e.g., https://api.example.com) |
| Path | The endpoint path (e.g., /orders/$orderId) |
| Method | GET, POST, PUT, or DELETE |
| Headers | JSON headers, often for authentication |
| Query Parameters | URL parameters (e.g., ?lang=en) |
| Request Body | JSON body for POST/PUT requests |
| Request Timeout | How long to wait before failing |
Dynamic arguments
Use $variableName syntax to create dynamic inputs. Each variable becomes a required argument the bot must provide when calling the action.
GET /orders/$orderIdPOST /stock
Body: { "productId": "$productId", "storeId": "$storeId" }Use camelCase for variable names and choose names that convey meaning — orderId, customerEmail, productSku.
Example: Check product stock
GET https://api.example.com/stock/$productId
Headers: { "x-api-key": "your-api-key" }The bot provides the product ID, and the action returns stock information. No code needed.
Python Actions
Python actions run a script when called. Use them when you need to:
- Call multiple APIs and combine the results
- Transform or validate data before or after an API call
- Add safety checks (e.g., verify a refund was actually offered before processing it)
- Handle complex input formats
- Tag conversations or set metadata as side effects
Structure
Every Python action implements an execute_action function that receives a context object and returns data for the AI:
import requests
def execute_action(context):
order_id = context["args"]["orderId"]
response = requests.get(
"https://api.example.com/orders",
params={"id": order_id},
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=30,
)
response.raise_for_status()
return response.json()The context object contains:
| Field | Description |
|---|---|
context["args"] | Arguments passed by the bot (e.g., orderId, email) |
context["conversation"] | Conversation data including messages and ID |
Return value: Whatever you return is sent back to the AI as context for its response. Return structured data (dicts, lists) or clear error messages.
Available helpers
Python actions have access to built-in helper functions. Click any function name for full documentation with parameters, return types, and examples.
Conversation data
| Helper | What it does |
|---|---|
get_conversation_metadata(context, key) | Read a metadata value from the conversation |
set_conversation_metadata(context, key, value) | Write a metadata value to the conversation |
delete_conversation_metadata(context, key) | Delete a metadata key from the conversation |
add_conversation_tag(context, tag) | Add a tag to the conversation |
add_conversation_event(context, event) | Record an event in the conversation timeline |
hand_off_conversation(context, reason=None) | Hand off the conversation to a human agent |
LLM classification
| Helper | What it does |
|---|---|
llm_classify_binary(prompt, input, fallback=None) | Classify input as True or False using an LLM |
llm_classify_category(prompt, input, options, fallback=None) | Classify input into one of the provided categories |
Experimentation
| Helper | What it does |
|---|---|
get_ab_test_variant(context, test_name, possible_variants) | Get a sticky A/B test variant for this conversation |
Utilities
| Helper | What it does |
|---|---|
add_google_sheets_row(sheet_id, data) | Append a row to a Google Sheet |
is_holiday(country, state=None, region=None, check_date=None) | Check if a date is a public holiday |
Examples
Fetch order with tracking from multiple APIs
This action calls an ERP for order data and a courier API for tracking events, then combines the results:
import requests
def execute_action(context):
order_id = context["args"]["orderId"]
email = context["args"]["email"]
# Fetch order from ERP
order_resp = requests.get(
"https://erp.example.com/orders",
params={"id": order_id, "email": email},
headers={"Authorization": "Bearer ERP_KEY"},
timeout=30,
)
if order_resp.status_code != 200:
return {"error": "Order not found. Please check the order ID."}
order = order_resp.json()
# Fetch tracking from courier
tracking = requests.get(
"https://tracking.example.com/shipments",
params={"tracking_number": order["trackingNumber"]},
timeout=30,
).json()
return {
"orderId": order_id,
"status": order["status"],
"trackingNumber": order["trackingNumber"],
"trackingEvents": tracking.get("events", []),
"estimatedDelivery": order.get("estimatedDelivery"),
}Process a refund with validation
This action checks the order state before processing a refund, and tags the conversation for analytics:
import requests
def execute_action(context):
order_id = context["args"]["orderId"]
reason = context["args"]["reason"]
# Validate order state
order = requests.get(
"https://api.example.com/orders",
params={"id": order_id},
timeout=30,
).json()
if order.get("isRefunded"):
return {"error": "This order has already been refunded."}
if not order.get("isCancellable"):
return {"error": "This order cannot be refunded in its current state."}
# Process the refund
result = requests.post(
"https://api.example.com/refund",
json={"orderId": order_id, "reason": reason, "fullRefund": True},
timeout=30,
).json()
# Tag for analytics
add_conversation_tag(context, "refunded")
set_conversation_metadata(context, "refund_amount", str(result["amount"]))
return {"success": True, "refundedAmount": result["amount"]}Create an order from chat
This action takes cart items collected by the bot and creates a pending order:
import requests
import json
def execute_action(context):
name = context["args"]["customerName"]
phone = context["args"]["phone"]
items = context["args"]["items"]
# Parse items if passed as string
if isinstance(items, str):
items = json.loads(items)
# Normalize phone number
if phone.startswith("+30"):
phone = phone[3:]
response = requests.post(
"https://api.example.com/orders",
json={
"customerName": name,
"phone": phone,
"items": items,
},
headers={"Authorization": "Bearer API_KEY"},
timeout=30,
)
response.raise_for_status()
result = response.json()
return {
"orderId": result["orderId"],
"total": result["total"],
"confirmationUrl": result["confirmationUrl"],
}Testing
Every action can be tested directly from the dashboard before you use it in a live workflow.
Testing API actions
- Open your API action in the dashboard
- The test panel automatically detects all
$variablesin your path, headers, query params, and body - Fill in test values for each variable
- Click Run Test
- The dashboard makes the actual HTTP request and shows the response
Testing Python actions
- Open your Python action in the dashboard
- Fill in test values for each argument
- Optionally enter a Conversation ID — this populates
context["conversation"]with real conversation data (messages, metadata, etc.). You can use any conversation from your dashboard — copy the ID or public ID from the conversation detail view - Click Run Test
- The dashboard executes your code and shows the returned result
Without a conversation ID, context["conversation"] is None. This is fine for actions that only use context["args"], but if your action reads conversation messages, metadata, or tags, provide a conversation ID to test with real data.
Tip: To test iteratively, keep a test conversation open in another tab. Send messages to it to set up the conversation state you want, then use its ID in the test panel.
Security and privacy
Your API response is visible to the bot and may be surfaced to the customer. Only return data that is appropriate for the end user to see.
Reducing sensitive exposure
If exact values are confidential, return abstracted indicators instead:
# Instead of this:
return {"stock": 123}
# Return this:
return {"inStock": True}Customer authentication
Consider requiring multiple matching identifiers when dealing with protected data:
- Arguments:
email+orderId - Your API filters with both values to verify ownership
Single-factor lookup (e.g., only order ID) improves UX but carries a small risk if the bot is manipulated. Choose based on your security requirements.
Best practices
- Name clearly.
getOrderDetailsnotgetData. The AI uses the name to understand the action. - Describe for the AI. Write descriptions as if briefing a teammate — "Retrieves order status and tracking info given an order ID."
- Return only what's needed. Don't expose raw database records. Return what the customer should see.
- Handle errors gracefully. Return clear error messages the AI can relay:
{"error": "Order not found"} - Use Python when logic is needed. If you need if/else, multiple API calls, or data transformation — use a Python action.
- Use API actions for simple lookups. One endpoint, one response, no transformation needed — an API action is simpler to maintain.
- Set timeouts. Always include a
timeoutparameter on HTTP requests to avoid hanging.