Common Patterns
Practical recipes for common bot automation scenarios — order tracking, refunds, product lookups, in-chat purchases, and more.
This page walks through common scenarios you'll encounter when building workflows. Each pattern shows the pieces you need — workflows, actions, and condition providers — with enough detail to get you started.
How can the bot offer discounts before cancelling?
Instead of cancelling immediately, the bot can try to retain the customer by offering a discount or partial refund — and only cancel if they insist.
What you need:
- Actions for applying a partial refund and cancelling the order/subscription
- A workflow with instructions that walk through escalating offers
Workflow instructions:
- Ask the customer why they want to cancel
- Based on the reason, offer a 15% partial refund to keep the order: "I understand — what if we applied a 15% discount to this order?"
- If they decline, offer 30%: "I can go up to 30% off — would that work?"
- If they still decline, confirm the cancellation and call
cancelOrder - After any accepted offer, call
processPartialRefundwith the agreed percentage
The bot follows the script naturally — it feels like a conversation, not a hard sell. You control exactly how many offers to make and at what thresholds.
With condition providers: For more control, add a condition provider that checks order state before the retention flow starts. This lets you skip the offers for orders that are already shipped (where cancellation isn't possible) or route to different offer levels based on order value, payment method, or customer history. See time-based policies and per-customer policies below.
With A/B testing: Not sure if 15%/30% is the right ladder? Use A/B testing to test different offer sequences and measure which retains more customers.
How can the bot take action when we don't have an API?
Not every system has an API. If you need the bot to "do" something — like request a refund, cancel a subscription, or unsubscribe from emails — but there's no API to call, use Google Sheets as a lightweight workaround.
The idea: the bot collects the information, writes it to a shared spreadsheet, and your team processes it from there. It's not full automation, but it means the customer gets an immediate confirmation and your team gets a structured, actionable queue instead of unstructured chat transcripts.
Action: recordRequest
from datetime import datetime
def execute_action(context):
request_type = context["args"]["requestType"]
email = context["args"]["email"]
order_id = context["args"].get("orderId", "")
reason = context["args"].get("reason", "")
add_google_sheets_row("1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", {
"Timestamp": datetime.now().isoformat(),
"Type": request_type,
"Email": email,
"Order ID": order_id,
"Reason": reason,
"Conversation ID": context["conversation"]["id"],
"Status": "Pending",
})
add_conversation_tag(context, f"request:{request_type}")
return {"success": True}Use this single action across multiple workflows — the requestType argument distinguishes them:
- Cancel order workflow → calls
recordRequestwithrequestType: "cancellation" - Refund request workflow → calls
recordRequestwithrequestType: "refund" - Email unsubscribe workflow → calls
recordRequestwithrequestType: "unsubscribe"
The bot confirms to the customer that their request has been submitted and your team will process it. Your team works through the spreadsheet, updating the "Status" column as they go.
Prerequisite: Share the Google Sheet with
[email protected](Editor access). The sheet needs a header row matching the column names in yourdatadict. Seeadd_google_sheets_rowfor details.
How can the bot track orders?
Connect a workflow to your order management system so the bot can look up an order and share its status with the customer.
What you need:
- A Python action that calls your OMS or ERP API
- A workflow that collects the order ID and calls the action
Action: getOrderDetails
import requests
def execute_action(context):
order_id = context["args"]["orderId"]
email = context["args"]["email"]
response = requests.get(
"https://api.example.com/orders",
params={"id": order_id, "email": email},
headers={"Authorization": "Bearer API_KEY"},
timeout=30,
)
if response.status_code != 200:
return {"error": "Order not found. Please double-check the order ID."}
order = response.json()
return {
"orderId": order["id"],
"status": order["status"],
"items": order["items"],
"trackingNumber": order.get("trackingNumber"),
"trackingUrl": order.get("trackingUrl"),
"estimatedDelivery": order.get("estimatedDelivery"),
}Workflow instructions:
- Ask for the customer's order ID and email address
- Call
getOrderDetailswith both values - Share the order status, items, and tracking link
- If there's no tracking number yet, explain the order is still being processed
- If the order wasn't found, ask the customer to double-check the details
Tip: Requiring both order ID and email adds a layer of customer authentication — the bot won't reveal order details to someone who only guesses an order number.
How can the bot get a logged-in user's info?
If the customer is logged in on your website, you can pass their identity to the bot automatically — no need to ask for their email or user ID.
How it works:
- Your website writes the user's info to
localStorageusing theoctocom:data:prefix - The chat widget sends it as conversation metadata
- Your Python actions read it with
get_conversation_metadata
On your website:
const user = getCurrentUser();
localStorage.setItem("octocom:data:user_id", user.id);
localStorage.setItem("octocom:data:email", user.email);
localStorage.setItem("octocom:data:name", user.fullName);In your action:
def execute_action(context):
user_id = get_conversation_metadata(context, "chat-widget:user_id")
if not user_id:
return {"error": "Customer is not logged in."}
# Use the user ID to fetch their data
response = requests.get(
"https://api.example.com/users",
params={"id": user_id},
headers={"Authorization": "Bearer API_KEY"},
timeout=30,
)
return response.json()The bot doesn't need to ask for the customer's identity — it already has it.
→ Full details on Chat Custom Data
How can the bot place an order?
Let the bot collect product selections and create an order directly in your system — turning the chat into a sales channel.
What you need:
- A Python action that creates orders via your API
- A workflow that guides the customer through product selection and checkout
Action: createOrder
import requests
import json
def execute_action(context):
name = context["args"]["customerName"]
phone = context["args"]["phone"]
items = context["args"]["items"]
if isinstance(items, str):
items = json.loads(items)
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"],
}Workflow instructions:
- Help the customer find products (use
findRelevantProductsif available) - Confirm the items and quantities — show a running total
- Collect the customer's name and phone number
- Call
createOrderwith the cart details - Share the order ID and confirmation link
- Do not collect payment or shipping details in chat — the confirmation link handles that
Why a Python action? The bot passes cart items as a JSON string. The action parses it, normalizes the phone number, and handles edge cases — things an API action can't do.
How can the bot check product stock in a store?
Let customers ask whether a product is available in a specific location.
What you need:
- An API action (or Python action) that checks your inventory system
- A workflow that collects the product and store, then calls the action
API action: getProductStock
GET https://api.example.com/stock/$productId?storeId=$storeId
Headers: { "x-api-key": "your-api-key" }This is a good fit for an API action — it's a single GET request with no logic needed.
Workflow instructions:
- Ask which product the customer is looking for
- Use
findRelevantProductsto identify the product and get its ID - Ask which store they want to check
- Call
getProductStockwith the product ID and store ID - Share the availability result
How can the bot get reviews for a product?
Surface product reviews so the bot can help customers make a decision.
What you need:
- A Python action that fetches reviews from your review system or feed
- A workflow triggered when the customer asks about reviews
Action: getProductReviews
import requests
def execute_action(context):
product_id = context["args"]["productId"]
response = requests.get(
"https://api.example.com/reviews",
params={"productId": product_id, "limit": 10},
headers={"Authorization": "Bearer API_KEY"},
timeout=30,
)
if response.status_code != 200:
return {"error": "Could not fetch reviews."}
data = response.json()
return {
"averageRating": data.get("averageRating"),
"totalReviews": data.get("totalReviews"),
"reviews": [
{"rating": r["rating"], "text": r["text"], "date": r["date"]}
for r in data.get("reviews", [])
],
}Workflow instructions:
- Identify which product the customer is asking about (use
findRelevantProductsif needed) - Call
getProductReviewswith the product ID - Summarize the reviews — mention the average rating and highlight 2-3 key themes
- Do not reveal reviewer names or emails
- If no reviews exist, let the customer know and offer to help with other product questions
How can the bot calculate shipping costs?
Let customers get a shipping estimate before checkout.
What you need:
- A Python action that calls your shipping API with the products and destination
- A workflow that collects the necessary details
Action: getShippingCost
import requests
import json
def execute_action(context):
zip_code = context["args"]["zipCode"]
items = context["args"]["items"]
if isinstance(items, str):
items = json.loads(items)
response = requests.post(
"https://api.example.com/shipping/calculate",
json={"zipCode": zip_code, "items": items},
headers={"Authorization": "Bearer API_KEY"},
timeout=30,
)
response.raise_for_status()
result = response.json()
return {
"cost": result["cost"],
"currency": result["currency"],
"estimatedDays": result["estimatedDays"],
"method": result["method"],
}Workflow instructions:
- Ask which products the customer wants to ship (or use the products from their current conversation)
- Ask for their zip/postal code
- Call
getShippingCostwith the product list and zip code - Present the cost and estimated delivery time
How can the bot handle refunds safely?
Process refunds automatically while making sure the order is actually eligible.
What you need:
- A Python action that validates the order state before processing
- A workflow (or variant) that guides the conversation
Action: processRefund
import requests
def execute_action(context):
order_id = context["args"]["orderId"]
reason = context["args"]["reason"]
# Fetch order to validate eligibility
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("isDelivered"):
return {"error": "This order hasn't been delivered yet."}
# Process the refund
result = requests.post(
"https://api.example.com/refund",
json={"orderId": order_id, "reason": reason, "fullRefund": True},
timeout=30,
).json()
add_conversation_tag(context, "refunded")
set_conversation_metadata(context, "refund_amount", str(result["amount"]))
return {"success": True, "refundedAmount": result["amount"]}Why this matters: The action checks the order state before touching anything. If the order is already refunded or hasn't been delivered, it returns an error instead of processing a duplicate refund. The bot receives the error and communicates it to the customer.
How can we enforce time-based refund policies?
Use a condition provider to check whether the order falls within your policy window — and route to different variants accordingly.
Condition provider:
import requests
from datetime import datetime, timedelta
def evaluate_conditions(context):
order_id = context["args"]["orderId"]
response = requests.get(
"https://api.example.com/orders",
params={"id": order_id},
timeout=30,
)
if response.status_code != 200:
return {"conditions": {"orderNotFound": True}, "data": {}}
order = response.json()
delivered_at = order.get("deliveredAt")
if not delivered_at:
return {
"conditions": {"orderNotFound": False, "notDelivered": True},
"data": {},
}
delivered = datetime.fromisoformat(delivered_at)
days_since = (datetime.now() - delivered).days
return {
"conditions": {
"orderNotFound": False,
"notDelivered": False,
"isRefunded": order.get("refundedAt") is not None,
"within14Days": days_since <= 14,
"within60Days": days_since <= 60,
},
"data": {
"daysSinceDelivery": days_since,
"orderTotal": order.get("total"),
},
}Variants:
| # | Variant | Conditions | What the AI does |
|---|---|---|---|
| 0 | Not found | orderNotFound | Ask customer to verify order ID |
| 1 | Not delivered | notDelivered | Explain the order hasn't arrived yet |
| 2 | Already refunded | isRefunded | Inform customer it's already been refunded |
| 3 | Within 14 days | within14Days | Process full refund — within standard return window |
| 4 | Within 60 days | within60Days | Offer partial refund or store credit — extended window |
| 5 | Default | (none) | Deny refund — outside policy window, explain why |
The condition provider does the date math. Each variant gets simple, focused instructions for its specific case.
How can we offer different policies to different customers?
Combine customer data with condition provider logic to route VIP customers, first-time buyers, or high-value orders to different treatment.
Condition provider:
import requests
def evaluate_conditions(context):
email = context["args"]["email"]
customer = requests.get(
"https://api.example.com/customers",
params={"email": email},
timeout=30,
).json()
total_spent = customer.get("totalSpent", 0)
order_count = customer.get("orderCount", 0)
return {
"conditions": {
"isVip": total_spent > 1000 or order_count > 10,
"isFirstOrder": order_count <= 1,
},
"data": {
"customerName": customer.get("name"),
"totalSpent": total_spent,
"orderCount": order_count,
},
}Variants:
| # | Variant | Conditions | What the AI does |
|---|---|---|---|
| 0 | VIP | isVip | Offer full refund, no questions asked. Apologize for the inconvenience |
| 1 | First order | isFirstOrder | Offer full refund with a discount code for their next order |
| 2 | Default | (none) | Follow standard return policy — collect reason, process per normal rules |
How can the bot generate a return label?
Call your shipping provider's API to create a return label and share it with the customer.
Action: generateReturnLabel
import requests
def execute_action(context):
order_id = context["args"]["orderId"]
# Fetch order for shipping details
order = requests.get(
"https://api.example.com/orders",
params={"id": order_id},
timeout=30,
).json()
if not order.get("shippingAddress"):
return {"error": "No shipping address found for this order."}
# Create return label via shipping provider
label = requests.post(
"https://api.example.com/returns/label",
json={
"orderId": order_id,
"fromAddress": order["shippingAddress"],
"weight": order.get("totalWeight", 1.0),
},
headers={"Authorization": "Bearer SHIPPING_KEY"},
timeout=30,
).json()
return {
"labelUrl": label["downloadUrl"],
"trackingNumber": label["trackingNumber"],
"carrier": label["carrier"],
"expiresAt": label["expiresAt"],
}Workflow instructions:
- Confirm the customer wants to return their order
- Call
generateReturnLabelwith the order ID - Share the label download link and tracking number
- Explain the return process (where to drop off, when to expect the refund)
How can the bot collect information for a B2B quote?
For businesses that sell to other businesses, the bot can qualify leads and collect quote details before handing off to the sales team.
Workflow instructions:
- Confirm the customer is a business or professional buyer
- Collect: company name, contact person, email, phone number
- Ask which products they're interested in (product codes and quantities)
- Ask for their delivery postal code
- Summarize the request and confirm it's correct
- Call
transferConversationto hand off to the commercial team
This is a pure instruction workflow — no custom actions needed. The bot structures the conversation so the sales team gets a complete, qualified request instead of a vague inquiry.
How can the bot deflect to self-service?
For order modifications, address changes, or similar requests where you have a self-service portal, the bot can collect details and direct the customer there instead of escalating.
Workflow instructions:
- Acknowledge the customer's request
- Collect the order ID so you have context
- Explain that this type of change can be made through the self-service portal
- Share the direct link to the relevant page
- Only escalate to a human if the customer says the portal isn't working or they've already tried
This reduces support volume without frustrating the customer — they get a direct link to solve their problem, and the bot only escalates when self-service genuinely fails.