AI Voice Agents for E-commerce
Deploy AI voice agents to transform your e-commerce customer experience. Handle order inquiries, delivery updates, COD confirmations, returns, and abandoned cart recovery at scale.
Industry Impact
| Metric | Before Voice AI | After Voice AI | Improvement |
|---|---|---|---|
| WISMO Calls | 100% to agents | 30% to agents | 70% reduction |
| Abandoned Cart Recovery | 2-5% (email) | 15-25% (voice) | 5-10x higher |
| COD Confirmation Rate | 60-70% | 85-95% | 25% increase |
| Return Processing Time | 24-48 hours | Real-time | Instant initiation |
| Customer Wait Time | 3-5 minutes | 0 seconds | Instant response |
Use Cases
1. Order Status Tracking (WISMO)
Automate "Where Is My Order?" calls - the #1 support inquiry for e-commerce.
{
"agent": {
"name": "Order Tracker",
"language": "en-US",
"llmProvider": "gemini-2.5",
"llmModel": "gemini-2.5-flash-lite",
"llmTemperature": 0.3,
"sttProvider": "deepgram",
"sttModel": "nova-3",
"sttConfig": {
"endpointing": 250,
"interimResults": true,
"keywords": ["order:2", "tracking:2", "delivered:2", "shipped:2"]
},
"ttsProvider": "cartesia",
"ttsVoice": "95856005-0332-41b0-935f-352e296aa0df",
"ttsConfig": {
"speed": 1.1
},
"greetingMessage": "Hello! Thanks for calling {{storeName}}. I can help you track your order. What's your order number or the phone number you used to place the order?",
"allowInterruptions": true,
"prompt": "..."
}
}
2. Delivery Updates (Outbound)
Proactively notify customers about their delivery status.
{
"agent": {
"name": "Delivery Notifier",
"type": "outbound",
"language": "en-US",
"greetingMessage": "Hi {{customerName}}! This is {{storeName}} calling about your order {{orderId}}. {{deliveryUpdate}}. Would you like me to repeat that or do you have any questions?",
"prompt": "..."
}
}
3. COD Order Confirmation
Confirm Cash-on-Delivery orders to reduce RTO (Return to Origin).
{
"agent": {
"name": "COD Confirmation",
"type": "outbound",
"language": "hi-IN",
"llmProvider": "gemini-2.5",
"llmModel": "gemini-2.5-flash-lite",
"sttProvider": "google",
"sttModel": "chirp",
"ttsProvider": "azure",
"ttsVoice": "hi-IN-SwaraNeural",
"greetingMessage": "Namaste {{customerName}} ji! Main {{storeName}} se bol rahi hoon. Aapne {{productName}} ka order diya hai, total amount {{orderTotal}} hai, aur delivery {{deliveryDate}} tak hogi. Kya aap is order ko confirm karte hain?",
"prompt": "..."
}
}
4. Cart Recovery
Re-engage customers who abandoned their shopping carts.
{
"agent": {
"name": "Cart Recovery",
"type": "outbound",
"greetingMessage": "Hi {{customerName}}! This is {{storeName}}. I noticed you left some items in your cart - the {{productName}}. Just wanted to check if you had any questions I could help with?",
"prompt": "..."
}
}
5. Return Initiation
Process return requests and schedule pickups automatically.
{
"agent": {
"name": "Returns Handler",
"greetingMessage": "Hello! I can help you with returns and exchanges. What would you like to return today?",
"prompt": "..."
}
}
System Prompts
Order Status Agent
You are a customer service agent for {{storeName}}, an e-commerce store. Your primary job is to help customers track their orders.
## Core Responsibilities
1. Look up order status using order ID or phone number
2. Provide clear delivery estimates
3. Explain tracking information
4. Handle delivery issues (delayed, lost, damaged)
## Conversation Flow
### Step 1: Identification
Ask for order ID or phone number:
- Order ID format: {{orderIdFormat}}
- If they don't have order ID, ask for phone number used during purchase
### Step 2: Order Lookup
Use the get_order_status tool with the provided identifier.
### Step 3: Status Communication
Based on the order status, provide clear information:
**Processing:**
"Your order is being prepared by our warehouse team. It should ship within {{processingTime}}."
**Shipped:**
"Great news! Your order shipped on [date] via [carrier]. Your tracking number is [spell out]. Expected delivery is [date]."
**Out for Delivery:**
"Exciting! Your order is out for delivery today. You should receive it by [time window]."
**Delivered:**
"Your order was delivered on [date] at [time]. If you haven't received it, please check with neighbors or your building security."
**Delayed:**
"I apologize - your order has been delayed due to [reason]. The new expected delivery is [date]. Would you like me to send you updates?"
## Response Guidelines
- Keep responses under 3 sentences
- Spell out order IDs digit by digit: "A B C 1 2 3 4 5"
- Give specific dates, not vague timeframes
- Offer next steps proactively
## Escalation Triggers
Transfer to human agent if:
- Package marked delivered but customer didn't receive
- Customer wants to file a complaint
- Order is delayed more than 7 days
- Damaged product claim
- Customer requests human explicitly
## Sample Conversations
Customer: "Where's my order?"
Agent: "I'd be happy to help track that! Could you give me your order number or the phone number you used when ordering?"
Customer: "It's 1234567"
Agent: "Let me look that up... Your order 1 2 3 4 5 6 7 shipped yesterday via BlueDart. It's expected to arrive by Thursday, December 28th. Would you like the tracking number?"
Customer: "It shows delivered but I didn't get it"
Agent: "I'm sorry to hear that. Let me connect you with our support team who can help investigate this right away. Please hold."
COD Confirmation Agent (Hindi)
Aap {{storeName}} ke customer service agent hain. Aapka kaam COD orders confirm karna hai.
## Call Objective
Order confirm karwana aur delivery details verify karna.
## Call Script
### Opening
"Namaste {{customerName}} ji! Main {{storeName}} se bol rahi hoon."
### Order Details
"Aapne {{productName}} ka order diya hai:
- Order number: {{orderId}}
- Amount: {{orderTotal}} rupees (Cash on Delivery)
- Delivery address: {{deliveryAddress}}
- Expected delivery: {{deliveryDate}}"
### Confirmation
"Kya yeh sab details sahi hain aur aap delivery ke time available rahenge?"
### Responses
**If Confirmed:**
"Bahut dhanyavaad! Aapka order confirm ho gaya hai. Delivery se pehle aapko SMS milega. Koi aur madad chahiye?"
**If Wants to Modify:**
"Bilkul! Kya change karna hai - address, payment method, ya products?"
**If Wants to Cancel:**
"Koi baat nahi. Main aapka order cancel kar deti hoon. Kya main jaanu ki cancel karne ka reason kya hai?"
(Use cancel_order tool, then record_cancellation_reason)
**If Not Available:**
"Koi problem nahi. Aap kab available honge? Main delivery reschedule kar deti hoon."
## Important Rules
- Always speak respectfully (ji, aap)
- Don't pressure customers to confirm
- If they want to cancel, process it politely
- Record reason for any cancellation
- Maximum 2 call attempts if no answer
Cart Recovery Agent
You are a helpful shopping assistant for {{storeName}}. A customer left items in their cart without completing checkout.
## Objective
Help the customer complete their purchase OR understand why they didn't.
## Opening Line
"Hi {{customerName}}! This is {{storeName}} calling. I noticed you were looking at {{productName}} earlier. Just wanted to check if you had any questions I could help with?"
## Key Guidelines
### Be Helpful, Not Pushy
- This is NOT a sales call - it's a service call
- Focus on removing obstacles, not closing sales
- If they're not interested, respect that immediately
### Common Reasons & Responses
**Price Concern:**
"I totally understand. Actually, I can check if we have any active offers... [check_available_offers] Looks like you can use code SAVE10 for 10% off!"
**Shipping Cost:**
"Good point about shipping. Orders over {{freeShippingThreshold}} ship free. You're at {{cartTotal}} - adding {{suggestedItem}} would get you free shipping and it's something that goes well with what you picked."
**Payment Issues:**
"Sorry you had trouble checking out. We accept cards, UPI, net banking, and COD. Would you like me to help you complete the order over the phone?"
**Just Browsing:**
"No problem at all! I'll let you browse. Is there anything specific you're looking for that I could help find?"
**Found It Cheaper:**
"Thanks for letting me know. We do have a price match policy - could you share where you found it cheaper? I might be able to match it."
**Not Sure About Fit/Quality:**
"Great question! [provide_product_details] Also, we have easy 7-day returns if it doesn't work out."
### Closing
If interested: "Great! Shall I send you a checkout link via SMS?"
If not interested: "No problem! Thanks for letting me know. Have a great day!"
## NEVER Do This
- Call more than once for the same abandoned cart
- Offer discounts immediately without understanding the issue
- Push after they've said no
- Make them feel guilty about not purchasing
Function Definitions
Order Management Tools
{
"tools": [
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Get order status, tracking information, and delivery estimate",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Order ID (e.g., ORD-123456)"
},
"phone_number": {
"type": "string",
"description": "Customer phone number to look up orders"
}
}
}
}
},
{
"type": "function",
"function": {
"name": "get_tracking_details",
"description": "Get real-time tracking information from shipping carrier",
"parameters": {
"type": "object",
"properties": {
"tracking_number": {
"type": "string",
"description": "Carrier tracking number"
},
"carrier": {
"type": "string",
"enum": ["bluedart", "delhivery", "ecom_express", "fedex", "ups", "dhl"],
"description": "Shipping carrier name"
}
},
"required": ["tracking_number", "carrier"]
}
}
},
{
"type": "function",
"function": {
"name": "confirm_cod_order",
"description": "Confirm a Cash-on-Delivery order",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string"
},
"confirmed": {
"type": "boolean",
"description": "Whether customer confirmed the order"
},
"modification_requested": {
"type": "string",
"description": "Any modifications requested (address, date, etc.)"
}
},
"required": ["order_id", "confirmed"]
}
}
},
{
"type": "function",
"function": {
"name": "cancel_order",
"description": "Cancel an order and process refund if applicable",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string"
},
"reason": {
"type": "string",
"enum": ["changed_mind", "found_cheaper", "delivery_too_slow", "wrong_item", "other"]
},
"reason_details": {
"type": "string",
"description": "Additional details about cancellation reason"
}
},
"required": ["order_id", "reason"]
}
}
},
{
"type": "function",
"function": {
"name": "reschedule_delivery",
"description": "Reschedule delivery to a different date or time",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string"
},
"new_date": {
"type": "string",
"description": "New preferred delivery date (YYYY-MM-DD)"
},
"time_slot": {
"type": "string",
"enum": ["morning", "afternoon", "evening"],
"description": "Preferred time slot"
}
},
"required": ["order_id", "new_date"]
}
}
}
]
}
Return & Refund Tools
{
"tools": [
{
"type": "function",
"function": {
"name": "initiate_return",
"description": "Start a return request for an order item",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string"
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sku": { "type": "string" },
"quantity": { "type": "integer" },
"reason": {
"type": "string",
"enum": ["damaged", "wrong_item", "not_as_described", "size_issue", "quality_issue", "changed_mind"]
}
}
},
"description": "Items to return"
},
"refund_method": {
"type": "string",
"enum": ["original_payment", "store_credit", "bank_transfer"],
"description": "Preferred refund method"
}
},
"required": ["order_id", "items"]
}
}
},
{
"type": "function",
"function": {
"name": "schedule_pickup",
"description": "Schedule a return pickup from customer address",
"parameters": {
"type": "object",
"properties": {
"return_id": {
"type": "string"
},
"pickup_date": {
"type": "string",
"description": "Preferred pickup date (YYYY-MM-DD)"
},
"time_slot": {
"type": "string",
"enum": ["morning", "afternoon", "evening"]
},
"address": {
"type": "string",
"description": "Pickup address (if different from order address)"
}
},
"required": ["return_id", "pickup_date"]
}
}
},
{
"type": "function",
"function": {
"name": "check_return_status",
"description": "Check the status of a return request",
"parameters": {
"type": "object",
"properties": {
"return_id": {
"type": "string"
},
"order_id": {
"type": "string"
}
}
}
}
}
]
}
Cart Recovery Tools
{
"tools": [
{
"type": "function",
"function": {
"name": "get_abandoned_cart",
"description": "Get details of customer's abandoned cart",
"parameters": {
"type": "object",
"properties": {
"customer_id": {
"type": "string"
},
"phone_number": {
"type": "string"
}
}
}
}
},
{
"type": "function",
"function": {
"name": "check_available_offers",
"description": "Check available discounts and offers for the cart",
"parameters": {
"type": "object",
"properties": {
"cart_id": {
"type": "string"
},
"category": {
"type": "string",
"description": "Product category to check offers for"
}
},
"required": ["cart_id"]
}
}
},
{
"type": "function",
"function": {
"name": "apply_discount",
"description": "Apply a discount code to the cart",
"parameters": {
"type": "object",
"properties": {
"cart_id": {
"type": "string"
},
"discount_code": {
"type": "string"
}
},
"required": ["cart_id", "discount_code"]
}
}
},
{
"type": "function",
"function": {
"name": "send_checkout_link",
"description": "Send a direct checkout link via SMS",
"parameters": {
"type": "object",
"properties": {
"cart_id": {
"type": "string"
},
"phone_number": {
"type": "string"
},
"include_discount": {
"type": "boolean",
"description": "Include any discussed discount in the link"
}
},
"required": ["cart_id", "phone_number"]
}
}
},
{
"type": "function",
"function": {
"name": "record_cart_abandonment_reason",
"description": "Record why customer didn't complete purchase",
"parameters": {
"type": "object",
"properties": {
"cart_id": {
"type": "string"
},
"reason": {
"type": "string",
"enum": ["price_too_high", "shipping_cost", "payment_issues", "found_cheaper", "just_browsing", "not_ready", "other"]
},
"notes": {
"type": "string"
}
},
"required": ["cart_id", "reason"]
}
}
}
]
}
Tool Handlers (Go)
Order Status Handler
package tools
import (
"context"
"fmt"
"strings"
"time"
)
type Order struct {
ID string
Status string
Items []OrderItem
TrackingNumber string
Carrier string
ShippedAt *time.Time
EstimatedDelivery *time.Time
DeliveredAt *time.Time
CustomerName string
DeliveryAddress string
}
type OrderItem struct {
SKU string
Name string
Quantity int
Price float64
}
func HandleGetOrderStatus(ctx context.Context, params map[string]any) (any, error) {
var order *Order
var err error
if orderID, ok := params["order_id"].(string); ok && orderID != "" {
order, err = orderService.GetByID(ctx, orderID)
} else if phone, ok := params["phone_number"].(string); ok && phone != "" {
orders, err := orderService.GetByPhone(ctx, phone)
if err != nil {
return nil, err
}
if len(orders) == 0 {
return map[string]any{
"found": false,
"message": "No orders found for this phone number. Please verify the number or provide an order ID.",
}, nil
}
if len(orders) == 1 {
order = orders[0]
} else {
return formatMultipleOrders(orders), nil
}
}
if err != nil || order == nil {
return map[string]any{
"found": false,
"message": "Order not found. Please verify the order number and try again.",
}, nil
}
return formatOrderForSpeech(order), nil
}
func formatOrderForSpeech(order *Order) map[string]any {
result := map[string]any{
"found": true,
"order_id": spellOut(order.ID),
"status": order.Status,
}
switch order.Status {
case "processing":
result["message"] = fmt.Sprintf(
"Your order %s is being prepared and will ship within 1-2 business days.",
spellOut(order.ID),
)
case "shipped":
result["message"] = fmt.Sprintf(
"Your order %s shipped on %s via %s. Tracking number is %s. Expected delivery is %s.",
spellOut(order.ID),
order.ShippedAt.Format("January 2"),
order.Carrier,
spellOut(order.TrackingNumber),
order.EstimatedDelivery.Format("Monday, January 2"),
)
result["tracking_number"] = order.TrackingNumber
result["carrier"] = order.Carrier
case "out_for_delivery":
result["message"] = fmt.Sprintf(
"Great news! Your order %s is out for delivery today and should arrive by evening.",
spellOut(order.ID),
)
case "delivered":
result["message"] = fmt.Sprintf(
"Your order %s was delivered on %s.",
spellOut(order.ID),
order.DeliveredAt.Format("Monday, January 2 at 3:04 PM"),
)
case "delayed":
result["message"] = fmt.Sprintf(
"I apologize, your order %s has been delayed. The new expected delivery is %s. We're sorry for the inconvenience.",
spellOut(order.ID),
order.EstimatedDelivery.Format("Monday, January 2"),
)
result["requires_escalation"] = true
}
return result
}
func formatMultipleOrders(orders []*Order) map[string]any {
orderList := make([]map[string]any, len(orders))
for i, order := range orders {
orderList[i] = map[string]any{
"order_id": spellOut(order.ID),
"status": order.Status,
"date": order.ShippedAt.Format("January 2"),
}
}
message := fmt.Sprintf("I found %d orders. ", len(orders))
for i, order := range orders {
message += fmt.Sprintf("Order %d: %s, status %s. ", i+1, spellOut(order.ID), order.Status)
}
message += "Which order would you like details on?"
return map[string]any{
"found": true,
"multiple": true,
"count": len(orders),
"orders": orderList,
"message": message,
}
}
func spellOut(s string) string {
// "ORD123456" -> "O R D 1 2 3 4 5 6"
return strings.Join(strings.Split(s, ""), " ")
}
COD Confirmation Handler
func HandleConfirmCODOrder(ctx context.Context, params map[string]any) (any, error) {
orderID := params["order_id"].(string)
confirmed := params["confirmed"].(bool)
order, err := orderService.GetByID(ctx, orderID)
if err != nil {
return nil, err
}
if confirmed {
err = orderService.ConfirmCOD(ctx, orderID)
if err != nil {
return nil, err
}
// Update analytics
metrics.Increment("cod.confirmed")
return map[string]any{
"success": true,
"message": fmt.Sprintf(
"Order confirmed. Your %s will be delivered by %s. Please keep %s rupees ready. You'll receive an SMS before delivery.",
order.Items[0].Name,
order.EstimatedDelivery.Format("Monday, January 2"),
formatCurrency(order.Total),
),
}, nil
}
// Handle non-confirmation
if modRequest, ok := params["modification_requested"].(string); ok {
return handleCODModification(ctx, order, modRequest)
}
return map[string]any{
"success": false,
"message": "I understand. Would you like to modify the order or cancel it?",
}, nil
}
func handleCODModification(ctx context.Context, order *Order, modification string) (map[string]any, error) {
switch {
case strings.Contains(modification, "address"):
return map[string]any{
"action": "collect_new_address",
"message": "Sure, I can update the delivery address. What's the new address?",
}, nil
case strings.Contains(modification, "date"):
return map[string]any{
"action": "collect_new_date",
"message": "When would you prefer delivery? We can do any day this week.",
}, nil
case strings.Contains(modification, "payment"):
return map[string]any{
"action": "change_payment",
"message": "Would you like to switch to prepaid? I can send you a payment link.",
}, nil
}
return map[string]any{
"action": "clarify",
"message": "What would you like to modify - the delivery address, delivery date, or payment method?",
}, nil
}
Cart Recovery Handler
type AbandonedCart struct {
ID string
CustomerID string
CustomerName string
Items []CartItem
Total float64
AbandonedAt time.Time
RecoveryAttempts int
}
type CartItem struct {
SKU string
Name string
Quantity int
Price float64
ImageURL string
}
func HandleGetAbandonedCart(ctx context.Context, params map[string]any) (any, error) {
var cart *AbandonedCart
var err error
if customerID, ok := params["customer_id"].(string); ok {
cart, err = cartService.GetAbandonedByCustomer(ctx, customerID)
} else if phone, ok := params["phone_number"].(string); ok {
cart, err = cartService.GetAbandonedByPhone(ctx, phone)
}
if err != nil || cart == nil {
return map[string]any{
"found": false,
"message": "No abandoned cart found.",
}, nil
}
// Format items for speech
itemDescription := formatCartItemsForSpeech(cart.Items)
return map[string]any{
"found": true,
"cart_id": cart.ID,
"items": cart.Items,
"total": cart.Total,
"item_count": len(cart.Items),
"message": fmt.Sprintf("You have %s in your cart, totaling %s.", itemDescription, formatCurrency(cart.Total)),
"abandoned_hours_ago": time.Since(cart.AbandonedAt).Hours(),
}, nil
}
func HandleCheckAvailableOffers(ctx context.Context, params map[string]any) (any, error) {
cartID := params["cart_id"].(string)
offers, err := promotionService.GetAvailableOffers(ctx, cartID)
if err != nil {
return nil, err
}
if len(offers) == 0 {
return map[string]any{
"has_offers": false,
"message": "I don't have any special offers available right now, but your cart items are already competitively priced!",
}, nil
}
// Find best offer
bestOffer := findBestOffer(offers)
return map[string]any{
"has_offers": true,
"best_offer": bestOffer,
"all_offers": offers,
"message": fmt.Sprintf("Good news! I found a %s discount code '%s' you can use. This would save you %s.", bestOffer.Description, bestOffer.Code, formatCurrency(bestOffer.Savings)),
}, nil
}
func HandleSendCheckoutLink(ctx context.Context, params map[string]any) (any, error) {
cartID := params["cart_id"].(string)
phone := params["phone_number"].(string)
includeDiscount := params["include_discount"].(bool)
// Generate checkout link
link, err := cartService.GenerateCheckoutLink(ctx, cartID, includeDiscount)
if err != nil {
return nil, err
}
// Send SMS
err = smsService.Send(ctx, phone, fmt.Sprintf(
"Complete your order: %s\nThis link expires in 24 hours.",
link,
))
if err != nil {
return map[string]any{
"success": false,
"message": "I had trouble sending the link. Can I try a different number?",
}, nil
}
// Track conversion attempt
metrics.Increment("cart_recovery.checkout_link_sent")
return map[string]any{
"success": true,
"message": "I've sent you a checkout link via SMS. It should arrive in just a few seconds. The link is valid for 24 hours.",
}, nil
}
func HandleRecordCartAbandonmentReason(ctx context.Context, params map[string]any) (any, error) {
cartID := params["cart_id"].(string)
reason := params["reason"].(string)
notes := params["notes"].(string)
err := analyticsService.RecordAbandonmentReason(ctx, cartID, reason, notes)
if err != nil {
return nil, err
}
// Track for analysis
metrics.Increment("cart_abandonment.reason." + reason)
return map[string]any{
"recorded": true,
"message": "Thank you for the feedback! We really appreciate it.",
}, nil
}
func formatCartItemsForSpeech(items []CartItem) string {
if len(items) == 1 {
return fmt.Sprintf("a %s", items[0].Name)
}
names := make([]string, len(items))
for i, item := range items {
names[i] = item.Name
}
if len(names) == 2 {
return fmt.Sprintf("%s and %s", names[0], names[1])
}
return fmt.Sprintf("%s, and %s", strings.Join(names[:len(names)-1], ", "), names[len(names)-1])
}
Platform Integrations
Shopify Integration
package shopify
import (
"context"
"encoding/json"
"fmt"
"net/http"
)
type ShopifyClient struct {
shopURL string
accessToken string
httpClient *http.Client
}
func NewShopifyClient(shopURL, accessToken string) *ShopifyClient {
return &ShopifyClient{
shopURL: shopURL,
accessToken: accessToken,
httpClient: &http.Client{Timeout: 10 * time.Second},
}
}
// GetOrder fetches order details from Shopify
func (c *ShopifyClient) GetOrder(ctx context.Context, orderID string) (*Order, error) {
url := fmt.Sprintf("https://%s/admin/api/2024-01/orders/%s.json", c.shopURL, orderID)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("X-Shopify-Access-Token", c.accessToken)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Order ShopifyOrder `json:"order"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return convertShopifyOrder(&result.Order), nil
}
// GetOrderByPhone finds orders by customer phone
func (c *ShopifyClient) GetOrderByPhone(ctx context.Context, phone string) ([]*Order, error) {
// First, find customer by phone
customersURL := fmt.Sprintf(
"https://%s/admin/api/2024-01/customers/search.json?query=phone:%s",
c.shopURL, phone,
)
// ... fetch customer, then their orders
ordersURL := fmt.Sprintf(
"https://%s/admin/api/2024-01/orders.json?customer_id=%s&status=any",
c.shopURL, customerID,
)
// ... fetch and return orders
}
// GetAbandonedCheckouts retrieves abandoned carts
func (c *ShopifyClient) GetAbandonedCheckouts(ctx context.Context) ([]*AbandonedCart, error) {
url := fmt.Sprintf(
"https://%s/admin/api/2024-01/checkouts.json?created_at_min=%s",
c.shopURL,
time.Now().Add(-72*time.Hour).Format(time.RFC3339),
)
// ... fetch abandoned checkouts
}
// GetFulfillment gets tracking info for an order
func (c *ShopifyClient) GetFulfillment(ctx context.Context, orderID string) (*Fulfillment, error) {
url := fmt.Sprintf(
"https://%s/admin/api/2024-01/orders/%s/fulfillments.json",
c.shopURL, orderID,
)
// ... fetch fulfillment with tracking details
}
func convertShopifyOrder(so *ShopifyOrder) *Order {
order := &Order{
ID: fmt.Sprintf("%d", so.ID),
Status: mapShopifyStatus(so.FulfillmentStatus),
CustomerName: so.Customer.FirstName + " " + so.Customer.LastName,
CustomerPhone: so.Customer.Phone,
DeliveryAddress: formatAddress(so.ShippingAddress),
Total: so.TotalPrice,
Currency: so.Currency,
CreatedAt: so.CreatedAt,
}
for _, item := range so.LineItems {
order.Items = append(order.Items, OrderItem{
SKU: item.SKU,
Name: item.Title,
Quantity: item.Quantity,
Price: item.Price,
})
}
if len(so.Fulfillments) > 0 {
fulfillment := so.Fulfillments[0]
order.TrackingNumber = fulfillment.TrackingNumber
order.Carrier = fulfillment.TrackingCompany
order.ShippedAt = &fulfillment.CreatedAt
}
return order
}
func mapShopifyStatus(status string) string {
switch status {
case "fulfilled":
return "shipped"
case "partial":
return "partially_shipped"
case "":
return "processing"
default:
return status
}
}
Shopify Webhook Handler
// Handle Shopify webhooks for real-time order updates
func HandleShopifyWebhook(w http.ResponseWriter, r *http.Request) {
topic := r.Header.Get("X-Shopify-Topic")
var payload json.RawMessage
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Invalid payload", http.StatusBadRequest)
return
}
switch topic {
case "orders/create":
handleOrderCreated(payload)
case "orders/fulfilled":
handleOrderFulfilled(payload)
case "fulfillments/update":
handleFulfillmentUpdate(payload)
case "checkouts/create":
handleCheckoutCreated(payload)
case "checkouts/update":
handleCheckoutAbandoned(payload)
}
w.WriteHeader(http.StatusOK)
}
func handleOrderFulfilled(payload json.RawMessage) {
var order ShopifyOrder
json.Unmarshal(payload, &order)
// Trigger outbound call for delivery notification
if order.Customer.Phone != "" {
triggerDeliveryNotificationCall(
order.Customer.Phone,
order.Customer.FirstName,
fmt.Sprintf("%d", order.ID),
order.Fulfillments[0].TrackingNumber,
)
}
}
func handleCheckoutAbandoned(payload json.RawMessage) {
var checkout ShopifyCheckout
json.Unmarshal(payload, &checkout)
// Check if checkout was completed
if checkout.CompletedAt != nil {
return // Not abandoned
}
// Wait period before cart recovery (e.g., 1 hour)
time.AfterFunc(1*time.Hour, func() {
// Verify still abandoned
current, _ := shopifyClient.GetCheckout(checkout.Token)
if current.CompletedAt != nil {
return // Was completed
}
// Trigger cart recovery call
if checkout.Phone != "" {
triggerCartRecoveryCall(
checkout.Phone,
checkout.Customer.FirstName,
checkout.Token,
)
}
})
}
WooCommerce Integration
package woocommerce
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
)
type WooCommerceClient struct {
siteURL string
consumerKey string
consumerSecret string
httpClient *http.Client
}
func NewWooCommerceClient(siteURL, consumerKey, consumerSecret string) *WooCommerceClient {
return &WooCommerceClient{
siteURL: siteURL,
consumerKey: consumerKey,
consumerSecret: consumerSecret,
httpClient: &http.Client{Timeout: 10 * time.Second},
}
}
func (c *WooCommerceClient) authHeader() string {
credentials := c.consumerKey + ":" + c.consumerSecret
return "Basic " + base64.StdEncoding.EncodeToString([]byte(credentials))
}
// GetOrder fetches order from WooCommerce
func (c *WooCommerceClient) GetOrder(ctx context.Context, orderID string) (*Order, error) {
url := fmt.Sprintf("%s/wp-json/wc/v3/orders/%s", c.siteURL, orderID)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", c.authHeader())
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var wooOrder WooOrder
if err := json.NewDecoder(resp.Body).Decode(&wooOrder); err != nil {
return nil, err
}
return convertWooOrder(&wooOrder), nil
}
// GetOrdersByPhone finds orders by billing phone
func (c *WooCommerceClient) GetOrdersByPhone(ctx context.Context, phone string) ([]*Order, error) {
// WooCommerce doesn't support phone search directly
// Use search or custom endpoint
url := fmt.Sprintf(
"%s/wp-json/wc/v3/orders?search=%s",
c.siteURL, phone,
)
// ... fetch and filter orders
}
// UpdateOrderStatus updates order status
func (c *WooCommerceClient) UpdateOrderStatus(ctx context.Context, orderID, status string) error {
url := fmt.Sprintf("%s/wp-json/wc/v3/orders/%s", c.siteURL, orderID)
payload := map[string]string{"status": status}
body, _ := json.Marshal(payload)
req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Authorization", c.authHeader())
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to update order: %d", resp.StatusCode)
}
return nil
}
// CreateRefund processes a refund
func (c *WooCommerceClient) CreateRefund(ctx context.Context, orderID string, amount float64, reason string) error {
url := fmt.Sprintf("%s/wp-json/wc/v3/orders/%s/refunds", c.siteURL, orderID)
payload := map[string]any{
"amount": fmt.Sprintf("%.2f", amount),
"reason": reason,
}
body, _ := json.Marshal(payload)
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Authorization", c.authHeader())
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
func convertWooOrder(wo *WooOrder) *Order {
order := &Order{
ID: fmt.Sprintf("%d", wo.ID),
Status: mapWooStatus(wo.Status),
CustomerName: wo.Billing.FirstName + " " + wo.Billing.LastName,
CustomerPhone: wo.Billing.Phone,
CustomerEmail: wo.Billing.Email,
DeliveryAddress: formatWooAddress(wo.Shipping),
Total: parseFloat(wo.Total),
Currency: wo.Currency,
CreatedAt: wo.DateCreated,
PaymentMethod: wo.PaymentMethodTitle,
}
for _, item := range wo.LineItems {
order.Items = append(order.Items, OrderItem{
SKU: item.SKU,
Name: item.Name,
Quantity: item.Quantity,
Price: parseFloat(item.Total),
})
}
// Get tracking from meta or shipment tracking plugin
if trackingMeta := findMeta(wo.MetaData, "_tracking_number"); trackingMeta != "" {
order.TrackingNumber = trackingMeta
}
return order
}
func mapWooStatus(status string) string {
switch status {
case "processing":
return "processing"
case "on-hold":
return "on_hold"
case "completed":
return "delivered"
case "pending":
return "pending_payment"
case "cancelled":
return "cancelled"
case "refunded":
return "refunded"
default:
return status
}
}
WooCommerce Webhook Handler
// Handle WooCommerce webhooks
func HandleWooCommerceWebhook(w http.ResponseWriter, r *http.Request) {
topic := r.Header.Get("X-WC-Webhook-Topic")
var payload json.RawMessage
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Invalid payload", http.StatusBadRequest)
return
}
switch topic {
case "order.created":
handleWooOrderCreated(payload)
case "order.updated":
handleWooOrderUpdated(payload)
}
w.WriteHeader(http.StatusOK)
}
func handleWooOrderCreated(payload json.RawMessage) {
var order WooOrder
json.Unmarshal(payload, &order)
// COD order - schedule confirmation call
if order.PaymentMethod == "cod" {
scheduleCallAfter(30*time.Minute, func() {
triggerCODConfirmationCall(
order.Billing.Phone,
order.Billing.FirstName,
fmt.Sprintf("%d", order.ID),
order.Total,
order.LineItems[0].Name,
)
})
}
}
Analytics & Metrics
Key Performance Indicators
type EcommerceVoiceMetrics struct {
// WISMO Metrics
WISMOCallsTotal int
WISMOCallsHandledByAI int
WISMOResolutionRate float64
WISMOAvgHandleTime time.Duration
// COD Confirmation
CODCallsAttempted int
CODCallsConnected int
CODConfirmationRate float64
RTOReductionPercent float64
// Cart Recovery
AbandonedCartsTargeted int
RecoveryCallsMade int
CartsRecovered int
RecoveryRate float64
RevenueRecovered float64
// Returns
ReturnRequestsHandled int
AutomatedPickupScheduled int
ReturnProcessingTime time.Duration
// Customer Satisfaction
CSAT float64
NPS int
}
func CalculateEcommerceMetrics(startDate, endDate time.Time) *EcommerceVoiceMetrics {
metrics := &EcommerceVoiceMetrics{}
// WISMO metrics
wismoStats := db.GetWISMOStats(startDate, endDate)
metrics.WISMOCallsTotal = wismoStats.Total
metrics.WISMOCallsHandledByAI = wismoStats.AIHandled
metrics.WISMOResolutionRate = float64(wismoStats.Resolved) / float64(wismoStats.AIHandled)
metrics.WISMOAvgHandleTime = wismoStats.TotalDuration / time.Duration(wismoStats.Total)
// COD metrics
codStats := db.GetCODStats(startDate, endDate)
metrics.CODCallsAttempted = codStats.Attempted
metrics.CODCallsConnected = codStats.Connected
metrics.CODConfirmationRate = float64(codStats.Confirmed) / float64(codStats.Connected)
// Calculate RTO reduction
previousRTORate := db.GetRTORate(startDate.AddDate(0, -1, 0), startDate)
currentRTORate := db.GetRTORate(startDate, endDate)
metrics.RTOReductionPercent = (previousRTORate - currentRTORate) / previousRTORate * 100
// Cart recovery metrics
cartStats := db.GetCartRecoveryStats(startDate, endDate)
metrics.AbandonedCartsTargeted = cartStats.Targeted
metrics.RecoveryCallsMade = cartStats.CallsMade
metrics.CartsRecovered = cartStats.Recovered
metrics.RecoveryRate = float64(cartStats.Recovered) / float64(cartStats.CallsMade)
metrics.RevenueRecovered = cartStats.TotalRecoveredValue
return metrics
}
Dashboard Tracking
func trackOrderStatusCall(call *Call, order *Order, resolved bool) {
// Call metrics
metrics.Increment("ecommerce.wismo.calls.total")
if resolved {
metrics.Increment("ecommerce.wismo.calls.resolved")
} else {
metrics.Increment("ecommerce.wismo.calls.escalated")
}
// Order status distribution
metrics.Increment("ecommerce.wismo.status." + order.Status)
// Timing
metrics.Histogram("ecommerce.wismo.duration", call.Duration.Seconds())
// Track by carrier for shipping issues
if order.Carrier != "" {
metrics.Increment("ecommerce.wismo.carrier." + order.Carrier)
}
}
func trackCartRecovery(cart *AbandonedCart, outcome string, revenue float64) {
metrics.Increment("ecommerce.cart_recovery.calls.total")
metrics.Increment("ecommerce.cart_recovery.outcome." + outcome)
if outcome == "recovered" {
metrics.Increment("ecommerce.cart_recovery.conversions")
metrics.Gauge("ecommerce.cart_recovery.revenue", revenue)
}
// Track abandonment reasons
if cart.AbandonmentReason != "" {
metrics.Increment("ecommerce.cart_abandonment.reason." + cart.AbandonmentReason)
}
}
func trackCODConfirmation(order *Order, confirmed bool, cancellationReason string) {
metrics.Increment("ecommerce.cod.calls.total")
if confirmed {
metrics.Increment("ecommerce.cod.confirmed")
metrics.Gauge("ecommerce.cod.confirmed_value", order.Total)
} else {
metrics.Increment("ecommerce.cod.cancelled")
metrics.Increment("ecommerce.cod.cancellation_reason." + cancellationReason)
}
}
ROI Calculator
type ROICalculation struct {
// Costs
VoiceAgentCost float64
CostPerMinute float64
TotalMinutes float64
// Savings
AgentCostSaved float64
CallsDeflected int
CostPerHumanCall float64
// Revenue Impact
CartsRecovered float64
RTOReduction float64
AverageOrderValue float64
// Totals
TotalCost float64
TotalBenefit float64
ROI float64
}
func CalculateROI(metrics *EcommerceVoiceMetrics, config *PricingConfig) *ROICalculation {
roi := &ROICalculation{}
// Voice agent costs
roi.TotalMinutes = float64(metrics.WISMOCallsHandledByAI) * 2.0 // avg 2 min per call
roi.CostPerMinute = 0.15 // Example: $0.15/min
roi.VoiceAgentCost = roi.TotalMinutes * roi.CostPerMinute
// Agent cost savings
roi.CallsDeflected = metrics.WISMOCallsHandledByAI
roi.CostPerHumanCall = 5.0 // Example: $5 per human-handled call
roi.AgentCostSaved = float64(roi.CallsDeflected) * roi.CostPerHumanCall
// Cart recovery revenue
roi.CartsRecovered = metrics.RevenueRecovered
// RTO reduction savings (for COD)
// Assume 5% RTO rate reduction saves $X per order
ordersWithReducedRTO := int(metrics.RTOReductionPercent * float64(metrics.CODCallsConnected))
rtoSavingsPerOrder := 15.0 // Example: $15 saved per avoided RTO
roi.RTOReduction = float64(ordersWithReducedRTO) * rtoSavingsPerOrder
// Calculate ROI
roi.TotalCost = roi.VoiceAgentCost
roi.TotalBenefit = roi.AgentCostSaved + roi.CartsRecovered + roi.RTOReduction
roi.ROI = (roi.TotalBenefit - roi.TotalCost) / roi.TotalCost * 100
return roi
}
Best Practices
1. Order Identification
Make it easy for customers to identify their orders:
// Support multiple identification methods
func identifyOrder(params map[string]any) (*Order, error) {
// Try order ID first (most specific)
if orderID, ok := params["order_id"].(string); ok {
return orderService.GetByID(orderID)
}
// Try phone + recent order
if phone, ok := params["phone_number"].(string); ok {
orders, _ := orderService.GetRecentByPhone(phone, 30*24*time.Hour)
if len(orders) == 1 {
return orders[0], nil
}
// Multiple orders - need disambiguation
}
// Try email
if email, ok := params["email"].(string); ok {
return orderService.GetRecentByEmail(email)
}
return nil, errors.New("could not identify order")
}
2. Handle Delivery Issues Gracefully
## Delivery Issue Handling
### Package Shows Delivered But Customer Didn't Receive
"I'm sorry to hear that. This is concerning and I want to make sure we resolve it quickly.
Let me connect you with our delivery specialist who can investigate this right away."
ā Transfer to human agent with context
### Package Delayed Beyond Estimate
"I apologize for the delay with your order.
[If reason known] It looks like [reason - weather, carrier delay, etc.].
[If unknown] Let me check with our shipping team and have someone call you back within 2 hours with an update.
As a gesture of apology, I'm adding a {{discountAmount}} credit to your account for your next order."
### Wrong Item Delivered
"I'm so sorry about that mix-up! Let me start a return for the wrong item right away,
and we'll ship out the correct item today with express shipping at no extra charge."
3. COD Confirmation Best Practices
// Optimal timing for COD confirmation calls
func scheduleCODConfirmation(order *Order) {
// Best conversion: 30-60 minutes after order
delay := 30 * time.Minute
// Adjust for time of day
now := time.Now()
callTime := now.Add(delay)
// Don't call before 9 AM or after 8 PM
if callTime.Hour() < 9 {
callTime = time.Date(callTime.Year(), callTime.Month(), callTime.Day(), 9, 0, 0, 0, callTime.Location())
} else if callTime.Hour() >= 20 {
// Schedule for next morning
callTime = time.Date(callTime.Year(), callTime.Month(), callTime.Day()+1, 10, 0, 0, 0, callTime.Location())
}
scheduler.Schedule(callTime, func() {
triggerCODConfirmationCall(order)
})
}
// Maximum 2 attempts for COD confirmation
func handleCODNoAnswer(order *Order, attemptNumber int) {
if attemptNumber >= 2 {
// Mark as unconfirmed, proceed with caution
orderService.MarkUnconfirmed(order.ID)
return
}
// Schedule retry after 2 hours
scheduler.Schedule(time.Now().Add(2*time.Hour), func() {
triggerCODConfirmationCall(order)
})
}
4. Cart Recovery Timing
// Optimal timing for cart recovery calls
func scheduleCartRecoveryCall(cart *AbandonedCart) {
// Timing based on cart value and abandonment pattern
var delay time.Duration
switch {
case cart.Total > 500: // High-value cart
delay = 1 * time.Hour // Call sooner
case cart.Total > 100:
delay = 2 * time.Hour
default:
delay = 4 * time.Hour
}
// Don't call on weekends for B2B
if cart.CustomerType == "business" && isWeekend(time.Now().Add(delay)) {
delay = timeUntilNextWeekday(time.Now().Add(delay))
}
scheduler.Schedule(time.Now().Add(delay), func() {
// Verify cart still abandoned
current, _ := cartService.Get(cart.ID)
if current.Status == "completed" {
return
}
triggerCartRecoveryCall(cart)
})
}
// Only one recovery call per cart
func canAttemptRecovery(cartID string) bool {
attempts := db.GetRecoveryAttempts(cartID)
return attempts == 0
}
5. Multilingual Support for Indian E-commerce
// Language selection based on customer preference
func determineLanguage(customer *Customer) string {
// Use customer's preferred language if set
if customer.PreferredLanguage != "" {
return customer.PreferredLanguage
}
// Infer from delivery state
stateLanguageMap := map[string]string{
"Maharashtra": "mr-IN",
"Tamil Nadu": "ta-IN",
"Karnataka": "kn-IN",
"Gujarat": "gu-IN",
"West Bengal": "bn-IN",
"Kerala": "ml-IN",
"Andhra Pradesh": "te-IN",
"Telangana": "te-IN",
}
if lang, ok := stateLanguageMap[customer.State]; ok {
return lang
}
// Default to Hindi for North India, English otherwise
northernStates := []string{"Delhi", "UP", "Bihar", "Rajasthan", "MP", "Punjab", "Haryana"}
for _, state := range northernStates {
if customer.State == state {
return "hi-IN"
}
}
return "en-IN"
}
Next Steps
- Order Status Agent - Basic order tracking example
- Customer Support - Full support agent
- Function Calling - Tool integration guide
- Multi-Language Support - Configure Indian languages
- Webhooks - Real-time order event integration