← Back to blog

Sales automation on Instagram and WhatsApp with bots

2026-05-10

Building Sales Automation Bots for Instagram and WhatsApp: A Developer's Guide

Social commerce is exploding. Over two billion people use Instagram monthly, and WhatsApp handles more than 100 billion messages every day. For developers, this represents a massive opportunity to build automated sales systems that engage customers where they already spend their time. This post dives into the technical architecture, APIs, and implementation patterns you need to create effective sales bots on both platforms.

Understanding the Platform Landscape

Before writing code, you need to understand how Instagram and WhatsApp differ technically. Instagram automation relies primarily on the Instagram Basic Display API and the Instagram Messaging API (part of the Meta Business Suite ecosystem). WhatsApp Business API, now evolved into the WhatsApp Business Platform, provides more robust messaging capabilities but comes with stricter approval processes and conversation-based pricing.

WhatsApp requires a Meta Business account, verified phone number, and approved message templates for outbound communication. Instagram bots can operate through direct messaging APIs once your app passes review. Both platforms ultimately route through Meta's infrastructure, which simplifies some architectural decisions.

Architecture for a Unified Sales Bot System

Your backend needs to handle incoming webhooks, maintain conversation state, integrate with product catalogs, and manage order flows. Here is a typical architecture:


┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Instagram     │     │   API Gateway   │     │   WhatsApp      │
│   Webhooks      │────▶│   / Web Server  │◀────│   Webhooks      │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                                │
                                ▼
                        ┌─────────────────┐
                        │  Bot Engine     │
                        │  (Node/Python)  │
                        └─────────────────┘
                                │
                ┌───────────────┼───────────────┐
                ▼               ▼               ▼
        ┌───────────┐   ┌───────────┐   ┌───────────┐
        │  Product  │   │   Order   │   │  Payment  │
        │  Catalog  │   │  Service  │   │  Gateway  │
        │   (DB)    │   │   (DB)    │   │  (Stripe) │
        └───────────┘   └───────────┘   └───────────┘

The core principle is platform-agnostic business logic with platform-specific adapters. This lets you reuse your sales funnel, inventory checks, and payment processing regardless of which channel the customer uses.

Setting Up WhatsApp Business API

To start with WhatsApp, you need to register with Meta's Business Partner or use the Cloud API directly. Here is how to send messages using the latest WhatsApp Cloud API:


import requests
import os
from typing import Dict, Optional

class WhatsAppClient:
    def __init__(self):
        self.access_token = os.getenv('WHATSAPP_ACCESS_TOKEN')
        self.phone_number_id = os.getenv('WHATSAPP_PHONE_NUMBER_ID')
        self.base_url = f"https://graph.facebook.com/v18.0/{self.phone_number_id}"
    
    def send_text_message(self, to_number: str, message: str) -> Dict:
        url = f"{self.base_url}/messages"
        headers = {
            "Authorization": f"Bearer {self.access_token}",
            "Content-Type": "application/json"
        }
        payload = {
            "messaging_product": "whatsapp",
            "recipient_type": "individual",
            "to": to_number,
            "type": "text",
            "text": {"body": message}
        }
        
        response = requests.post(url, headers=headers, json=payload)
        return response.json()
    
    def send_product_catalog(self, to_number: str, catalog_id: str) -> Dict:
        # Interactive message with product list
        url = f"{self.base_url}/messages"
        headers = {
            "Authorization": f"Bearer {self.access_token}",
            "Content-Type": "application/json"
        }
        payload = {
            "messaging_product": "whatsapp",
            "recipient_type": "individual",
            "to": to_number,
            "type": "interactive",
            "interactive": {
                "type": "product_list",
                "header": {
                    "type": "text",
                    "text": "Our Products"
                },
                "body": {
                    "text": "Browse our catalog and tap to order"
                },
                "action": {
                    "catalog_id": catalog_id,
                    "sections": [{
                        "title": "Best Sellers",
                        "product_items": [
                            {"product_retailer_id": "sku_001"},
                            {"product_retailer_id": "sku_002"}
                        ]
                    }]
                }
            }
        }
        
        response = requests.post(url, headers=headers, json=payload)
        return response.json()

Notice the product_retailer_id values. These must match your Meta Commerce catalog exactly. Keeping catalog synchronization between your inventory system and Meta's platform is critical for consistent product availability.

Instagram Messaging API Implementation

Instagram messaging requires different endpoint structures. You interact with Instagram Professional accounts through the Conversations API. Here is a Python implementation:


class InstagramClient:
    def __init__(self):
        self.access_token = os.getenv('INSTAGRAM_ACCESS_TOKEN')
        self.base_url = "https://graph.facebook.com/v18.0"
    
    def send_message(self, recipient_ig_id: str, message: str) -> Dict:
        # First, get the conversation thread
        thread_url = f"{self.base_url}/me/conversations"
        params = {
            "access_token": self.access_token,
            "fields": "participants",
            "user_id": recipient_ig_id
        }
        
        # Actually, for Instagram direct messages, use the messages edge
        url = f"{self.base_url}/me/messages"
        headers = {
            "Authorization": f"Bearer {self.access_token}",
            "Content-Type": "application/json"
        }
        payload = {
            "recipient": {"id": recipient_ig_id},
            "message": {"text": message},
            "messaging_type": "RESPONSE"
        }
        
        response = requests.post(url, headers=headers, json=payload)
        return response.json()
    
    def send_carousel_products(self, recipient_ig_id: str, products: list):
        # Instagram supports generic templates with carousel elements
        elements = []
        for product in products[:10]:  # Max 10 elements
            elements.append({
                "title": product['name'][:80],
                "subtitle": f"${product['price']} - {product['description'][:80]}",
                "image_url": product['image_url'],
                "buttons": [{
                    "type": "postback",
                    "title": "Add to Cart",
                    "payload": f"ADD_CART:{product['sku']}"
                }, {
                    "type": "web_url",
                    "url": product['checkout_url'],
                    "title": "Buy Now"
                }]
            })
        
        payload = {
            "recipient": {"id": recipient_ig_id},
            "message": {
                "attachment": {
                    "type": "template",
                    "payload": {
                        "template_type": "generic",
                        "elements": elements
                    }
                }
            }
        }
        
        url = f"{self.base_url}/me/messages"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        response = requests.post(url, headers=headers, json=payload)
        return response.json()

The postback button type is particularly valuable. When users tap these, Meta sends a webhook event to your server with the payload you defined, allowing seamless cart manipulation without leaving the chat.

Webhook Handling and Conversation State

Both platforms deliver events via webhooks. Your endpoint must verify subscriptions and parse incoming events. Here is a FastAPI-based webhook handler that works for both platforms:


from fastapi import FastAPI, Request, Response
from fastapi.responses import PlainTextResponse
import hashlib
import hmac
import json
import os

app = FastAPI()
VERIFY_TOKEN = os.getenv('META_VERIFY_TOKEN')
APP_SECRET = os.getenv('META_APP_SECRET')

class ConversationManager:
    def __init__(self):
        self.states = {}  # Redis in production
    
    def get_state(self, user_id: str) -> dict:
        return self.states.get(user_id, {"stage": "greeting", "cart": []})
    
    def update_state(self, user_id: str, state: dict):
        self.states[user_id] = state

conv_manager = ConversationManager()

@app.get("/webhook")
async def verify_webhook(request: Request):
    mode = request.query_params.get("hub.mode")
    token = request.query_params.get("hub.verify_token")
    challenge = request.query_params.get("hub.challenge")
    
    if mode == "subscribe" and token == VERIFY_TOKEN:
        return PlainTextResponse(content=challenge, status_code=200)
    return Response(status_code=403)

@app.post("/webhook")
async def receive_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get("X-Hub-Signature-256", "")
    
    # Verify webhook signature for security
    expected = hmac.new(
        APP_SECRET.encode(),
        body,
        hashlib.sha256
    ).hexdigest()
    
    if not hmac.compare_digest(f"sha256={expected}", signature):
        return Response(status_code=401)
    
    data = json.loads(body)
    
    for entry in data.get("entry", []):
        platform = "instagram" if "messaging" in str(entry) else "whatsapp"
        
        for event in entry.get("messaging", entry.get("changes", [])):
            # Extract message data regardless of platform
            if platform == "instagram":
                message = event.get("message", {})
                sender_id = event.get("sender", {}).get("id")
            else:
                # WhatsApp structure differs
                value = event.get("value", {})
                message = value.get("messages", [{}])[0]
                sender_id = message.get("from")
            
            await process_incoming_message(platform, sender_id, message)
    
    return PlainTextResponse(content="EVENT_RECEIVED")

async def process_incoming_message(platform: str, user_id: str, message: dict):
    text = message.get("text", {}).get("body", "").lower()
    state = conv_manager.get_state(user_id)
    
    # Simple state machine for sales funnel
    if state["stage"] == "greeting":
        response = "Welcome! What are you looking for today? Reply BROWSE or TRACK"
        state["stage"] = "awaiting_intent"
    
    elif text in ["browse", "catalog", "products"]:
        products = await fetch_recommended_products(user_id)
        if platform == "whatsapp":
            await whatsapp_client.send_product_catalog(user_id, "main_catalog")
        else:
            await instagram_client.send_carousel_products(user_id, products)
        state["stage"] = "browsing"
        return
    
    elif text.startswith("add_cart"):
        sku = text.split(":")[1]
        state["cart"].append(sku)
        response = f"Added to cart! You have {len(state['cart'])} items. CHECKOUT or continue browsing?"
        state["stage"] = "cart_review"
    
    elif text == "checkout":
        order = await create_order(user_id, state["cart"])
        response = f"Order #{order['id']} created! Pay here: {order['payment_url']}"
        state["stage"] = "payment_pending"
    
    else:
        response = "I didn't understand. Try: BROWSE, CART, or TRACK [order_id]"
    
    conv_manager.update_state(user_id, state)
    
    if platform == "whatsapp":
        await whatsapp_client.send_text_message(user_id, response)
    else:
        await instagram_client.send_message(user_id, response)

State management deserves serious attention. The in-memory dictionary above works for prototyping, but production systems need Redis or DynamoDB with TTL policies. Conversation persistence also enables analytics and abandoned cart recovery.

Handling Media and Rich Content

Sales bots must process images for visual search, voice messages for accessibility, and documents for receipts. Here is how to handle incoming media on WhatsApp:


async def download_media(media_id: str, platform: str) -> bytes:
    # Get media URL from Meta's servers
    url = f"https://graph.facebook.com/v18.0/{media_id}"
    headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}
    
    resp = requests.get(url, headers=headers)
    media_url = resp.json().get("url")
    
    # Download actual binary
    media_resp = requests.get(media_url, headers=headers)
    return media_resp.content

async def process_image_message(user_id: str, media_id: str):
    image_bytes = await download_media(media_id, "whatsapp")
    
    # Upload to your visual search service
    similar_products = await visual_search.find_similar(image_bytes)
    
    response = "I found these similar items:"
    await send_product_recommendations(user_id, similar_products)

Instagram's API has stricter media processing limitations. For product recognition from Instagram DMs, consider routing users to your app or website for full visual search capabilities.

Order Fulfillment and Post-Purchase Automation

The sale does not end at payment. Your bot must handle shipping updates and support requests. WhatsApp template messages are essential here because they allow proactive notifications outside the 24-hour window:


class NotificationTemplates:
    SHIPPING_UPDATE = "shipping_update_v2"
    DELIVERY_CONFIRMED = "delivery_confirmed_v1"
    
    @staticmethod
    def prepare_template(to_number: str, template_name: str, params: list):
        return {
            "messaging_product": "whatsapp",
            "recipient_type": "individual",
            "to": to_number,
            "type": "template",
            "template": {
                "name": template_name,
                "language": {"code": "en_US"},
                "components": [{
                    "type": "body",
                    "parameters": [
                        {"type": "text", "text": p} for p in params
                    ]
                }]
            }
        }

# Usage: Notify customer of shipment
template_data = NotificationTemplates.prepare_template(
    "+1234567890",
    NotificationTemplates.SHIPPING_UPDATE,
    ["ORD-7829", "FedEx", "7845123695"]
)

Template approval from Meta takes 1-24 hours typically. Submit templates for all your standard notifications during development to avoid delays at launch.

Rate Limits, Error Handling, and Compliance

Meta enforces strict rate limits. WhatsApp Cloud API allows roughly 80 messages per second per number at the standard tier. Instagram messaging has lower limits for newer apps. Implement exponential backoff:


import asyncio
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(5),
    wait=wait_exponential(multiplier=1, min=2, max=60),
    retry=lambda e: isinstance(e, requests.exceptions.HTTPError) and 
                   e.response.status_code in [429, 503]
)
async def send_with_retry(client_method, *args, **kwargs):
    return await client_method(*args, **kwargs)

Compliance is equally critical. Both platforms prohibit unsolicited messaging. WhatsApp requires opt-in before business-initiated conversations. Instagram allows responses to user-initiated messages freely. Always provide clear opt-out mechanisms and respect regional privacy laws like GDPR and LGPD.

Measuring Bot Performance

Track conversation analytics separately from standard web analytics. Key metrics include:

Store structured conversation logs for analysis. A simple schema might include conversation_id, platform, user_id, events (array of message/response pairs with timestamps), outcome (abandoned, ordered, escalated), and revenue.

Conclusion

Building sales automation bots for Instagram and WhatsApp requires navigating Meta's platform APIs, managing conversation state intelligently, and designing friction-free purchase flows within chat interfaces. The technical foundations are solid, the APIs are maturing rapidly, and the commercial opportunity is substantial. Start with a narrow use case, perhaps WhatsApp catalog browsing or Instagram DM product recommendations, measure rigorously, and expand based on real user behavior patterns.

The platforms will continue converging under Meta's umbrella. Investing in unified, platform-agnostic architecture now positions your systems for whatever messaging innovations come next.