Back to blog
Chatmaid DevelopersAug 05, 2026·6 min read

How to Build a WhatsApp Order Tracking Bot for E-Commerce

An order tracking bot running on Chatmaid + n8n eliminates the most repetitive category of e-commerce support — at a cost of under $10/month in infrastructure. Your support team handles exceptions, disputes, and relationship-building. The bot handles "where is my order?" at scale and at any hour. Start free: developers.chatmaid.net/signup

How to Build a WhatsApp Order Tracking Bot for E-Commerce

"Where is my order?" is the single most common customer service question in e-commerce. It accounts for 25-40% of all support tickets in most online stores. Every one of those tickets requires a human to look up an order number, check a tracking link, and type a reply — work that takes 2-3 minutes per ticket and scales linearly with your order volume.

A WhatsApp order tracking bot answers this question instantly, at any hour, without any human involvement. This guide shows you how to build one.

What the Bot Handles

The bot we're building covers the full post-purchase support flow:

  • Order status — has it shipped, is it in transit, has it been delivered?
  • Tracking information — the carrier and tracking number, with a link
  • Estimated delivery — when to expect the package
  • Delivery issues — delayed, returned to sender, wrong address
  • Return/exchange requests — initiates the process and creates a ticket
  • Escalation — routes complex issues to a human agent

Architecture

Customer WhatsApp message ↓ Chatmaid webhook → Your server / n8n ↓ Parse intent (order lookup, tracking, return, etc.) ↓ Query order management system (Shopify / WooCommerce / database) ↓ Format response ↓ Chatmaid send → Customer WhatsApp reply

The key integration point is your order management system. The bot needs to look up orders by order number or phone number to return accurate status information.

Part 1: The Basic Order Lookup Flow

Identifying the Order

Customers contact you in several ways:

  • "Where is order #4892?"
  • "My order hasn't arrived" (no order number)
  • "I ordered last week and need tracking"

Use an AI model to extract the order number if present, or ask for it if not:

python

import re def extract_order_number(message: str) -> str | None: # Look for common order number patterns patterns = [ r'#(\d{4,})', # #4892 r'order[:\s]+(\d{4,})', # order: 4892 or order 4892 r'(\d{4,})', # standalone number ] for pattern in patterns: match = re.search(pattern, message, re.IGNORECASE) if match: return match.group(1) return None

If no order number is found, reply asking for it:

"Hi! I'd be happy to help track your order. Could you share your order number? You'll find it in your confirmation email (it starts with #)."

Querying Order Status

Shopify:

python

import shopify def get_order_status(order_number: str) -> dict: shopify.ShopifyResource.set_site(f"https://{SHOP_NAME}.myshopify.com/admin/api/2024-01") shopify.ShopifyResource.set_headers({ 'X-Shopify-Access-Token': SHOPIFY_ACCESS_TOKEN }) orders = shopify.Order.find(name=f"#{order_number}", status='any') if not orders: return {"found": False} order = orders[0] fulfillment = order.fulfillments[0] if order.fulfillments else None return { "found": True, "order_number": order.name, "status": order.fulfillment_status, # unfulfilled, fulfilled, partial "financial_status": order.financial_status, "line_items": [i.title for i in order.line_items], "tracking_number": fulfillment.tracking_number if fulfillment else None, "tracking_company": fulfillment.tracking_company if fulfillment else None, "tracking_url": fulfillment.tracking_url if fulfillment else None, "created_at": str(order.created_at), }

WooCommerce:

python

from woocommerce import API wcapi = API( url=STORE_URL, consumer_key=WC_KEY, consumer_secret=WC_SECRET, version="wc/v3" ) def get_woo_order(order_number: str) -> dict: orders = wcapi.get("orders", params={"number": order_number}).json() if not orders: return {"found": False} order = orders[0] return { "found": True, "status": order["status"], "line_items": [i["name"] for i in order["line_items"]], "tracking": order.get("meta_data", []) }

Generic database:

python

def get_order_from_db(order_number: str) -> dict: conn = get_db_connection() order = conn.execute( "SELECT * FROM orders WHERE order_number = ?", (order_number,) ).fetchone() if not order: return {"found": False} return dict(order)

Formatting the Reply

Map order statuses to human-readable WhatsApp messages:

python

def format_order_reply(order: dict) -> str: if not order["found"]: return ( "I couldn't find an order with that number. " "Please double-check and try again, or contact our support team at support@yourstore.com" ) status = order["status"] items = ", ".join(order["line_items"][:3]) # Max 3 items if status in ["unfulfilled", "pending"]: return ( f"📦 Order {order['order_number']}\n" f"Items: {items}\n\n" f"Status: Being prepared — your order is confirmed and we're getting it ready to ship. " f"You'll receive a WhatsApp notification with tracking as soon as it ships!" ) elif status in ["fulfilled", "shipped"]: tracking_line = "" if order.get("tracking_number"): tracking_line = ( f"\n🚚 Carrier: {order['tracking_company']}\n" f"Tracking: {order['tracking_number']}\n" f"Track here: {order['tracking_url']}" ) return ( f"📦 Order {order['order_number']}\n" f"Status: *Shipped* ✅{tracking_line}\n\n" f"Is there anything else I can help with?" ) elif status == "delivered": return ( f"✅ Order {order['order_number']} was delivered!\n\n" f"If you haven't received your package or there's an issue, " f"reply *PROBLEM* and I'll connect you with our team right away." ) elif status in ["cancelled", "refunded"]: return ( f"Your order {order['order_number']} was {status}. " f"If you have questions about your refund, reply *REFUND* " f"and I'll pull up the details." ) else: return ( f"📦 Order {order['order_number']} — Status: {status}\n\n" f"For more details, please contact us at support@yourstore.com " f"or reply *AGENT* to speak with our team." )

Part 2: Handling Returns and Exchanges

When a customer replies with "I want to return this" or "RETURN", trigger the returns flow:

python

RETURN_KEYWORDS = ["return", "exchange", "refund", "defective", "wrong item", "damaged"] def detect_return_intent(message: str) -> bool: return any(kw in message.lower() for kw in RETURN_KEYWORDS) def handle_return_request(phone: str, order_number: str): # 1. Reply to customer send_whatsapp( phone, f"I'm sorry to hear there's an issue with your order! " f"I've flagged this for our returns team and created a ticket.\n\n" f"A team member will contact you within 24 hours. " f"Our returns window is 30 days from delivery, and return shipping is free for defective items.\n\n" f"Your case number: RET-{generate_case_id()}" ) # 2. Create internal ticket (email, Slack, or your ticketing system) create_support_ticket({ "type": "return_request", "customer_phone": phone, "order_number": order_number, "source": "whatsapp_bot" })

Part 3: Proactive Order Notifications (Outbound)

Don't wait for customers to ask — send status updates automatically.

On order placed (webhook from Shopify/WooCommerce):

python

@app.route('/webhooks/order-created', methods=['POST']) def order_created(): order = request.json customer_phone = order.get('shipping_address', {}).get('phone') if customer_phone: send_whatsapp( customer_phone, f"Thanks for your order! 🎉\n\n" f"Order #{order['order_number']} is confirmed.\n" f"Items: {', '.join(i['name'] for i in order['line_items'][:3])}\n\n" f"We'll message you as soon as it ships. " f"Reply *STATUS* anytime to check your order." ) return jsonify({}), 200

On order shipped:

python

@app.route('/webhooks/order-fulfilled', methods=['POST']) def order_fulfilled(): order = request.json fulfillment = order.get('fulfillments', [{}])[0] customer_phone = order.get('shipping_address', {}).get('phone') if customer_phone: tracking = fulfillment.get('tracking_number', 'N/A') tracking_url = fulfillment.get('tracking_url', '') message = ( f"🚚 Your order #{order['order_number']} has shipped!\n\n" f"Carrier: {fulfillment.get('tracking_company', 'Standard shipping')}\n" f"Tracking: {tracking}" ) if tracking_url: message += f"\nTrack here: {tracking_url}" message += "\n\nReply *STATUS* anytime to check your delivery progress." send_whatsapp(customer_phone, message) return jsonify({}), 200

Part 4: Building in n8n (No-Code Version)

If you're not writing custom code, here's the n8n workflow:

Trigger: Webhook (receives Chatmaid message.received events)

Node 2: Extract order number from message using a Code node (the regex above)

Node 3: If no order number — send a request message via Chatmaid HTTP Request

Node 4: HTTP Request to your Shopify/WooCommerce API to fetch order data

Node 5: Switch node — routes by order status (unfulfilled / shipped / delivered / cancelled)

Node 6 (per branch): Set node that formats the appropriate reply text

Node 7: HTTP Request to Chatmaid send endpoint

Node 8: Optional — Google Sheets log of the interaction

Part 5: The AI Upgrade

Once the basic flow works, upgrade the reply generation to use an AI model. This handles:

  • Ambiguous messages ("my stuff hasn't arrived" — no order number)
  • Multi-question messages ("where is my order and can I change the address?")
  • Emotional messages ("this is taking way too long, I need this for an event tomorrow")
  • Languages other than English

Pass the customer message, order status data, and a system prompt to GPT-4o or Claude, and let the AI compose the reply using the structured data you fetched.

Metrics to Track

  • Containment rate: % of order inquiries resolved without human agent
  • Average resolution time: Should be under 10 seconds
  • Escalation rate: % that route to human (target: under 20%)
  • Customer satisfaction: Add a simple 👍/👎 reaction prompt after resolution