Most WhatsApp bot tutorials stop at echoing a message back. That is not a product. The hard parts of an AI chatbot on WhatsApp are all in the boring middle: what the model is allowed to know, what it is allowed to do, when it must shut up and fetch a human, and who pays when a template fires.
I have built this stack more than once, most substantially on AI Walay, a multi-tenant WhatsApp Business SaaS, where every tenant had their own phone number, their own knowledge base and their own escalation rules. Below is the architecture I would rebuild tomorrow, plus the mistakes that cost me the most time.
What does it take to build an AI chatbot on WhatsApp?
Five components: a verified WhatsApp Business Account with a Cloud API phone number, a public HTTPS webhook, a conversation store, an LLM call with tool definitions, and a send path back to the Cloud API messages endpoint. Everything else is decoration.
Meta's Cloud API docs describe webhooks as JSON payloads delivered to your server for inbound messages and status updates. Your service is therefore stateless from Meta's point of view; all memory, identity and business logic live on your side. If you have not provisioned the number yet, start with the step-by-step Cloud API setup and come back here.
The moving parts, and what I use for each:
- Transport: Meta Cloud API directly. BSPs like Twilio or 360dialog add convenience and a markup; go direct if you have an engineer.
- Webhook: FastAPI on a small container, returning 200 in under five seconds and doing real work in a background task.
- State: Postgres or Supabase, one row per message plus a per-contact summary row. Redis for the dedupe set.
- Model: OpenAI or Anthropic Claude with tool calling. Model choice matters far less than context discipline.
- Retrieval: pgvector over your product and policy docs, filtered by tenant, top-k small.
"Answer the webhook immediately and process asynchronously. Meta retries on timeout, and a slow LLM call is the fastest way to send the same customer three replies."
How does the 24-hour session window change the design?
It splits your bot into two products. Inside the 24-hour customer service window, which opens when the user messages you and resets on each new inbound message, you can send free-form text. Outside it, you can only send templates that Meta has pre-approved.
This is not a billing footnote, it is a state machine. Every outbound message in your system needs to check the window before it picks a send method. I store window_expires_at on the contact row, bump it on every inbound webhook, and make the send function refuse to emit free text when the timestamp has passed.
Session messages versus template messages, the distinction the whole architecture hangs on.
| Session (free-form) | Template (pre-approved) | |
|---|---|---|
| When allowed | Inside the 24-hour customer service window | Any time, including a cold outbound |
| Content | Anything the LLM generates, within policy | Fixed body with variable placeholders only |
| Approval | None | Submitted and reviewed by Meta before first use |
| Categories | Not applicable | Marketing, utility, authentication |
| Typical use | The actual AI conversation | Re-engagement, order updates, OTPs |
| Billing shape | Free-form replies in-window are not charged | Charged per delivered template, priced by category |
One design consequence people miss: your AI should never be the thing that reopens a dead conversation. Templates are static and approved, so a model cannot write its way out of a closed window. Build a small set of utility templates that re-engage with a legitimate reason, then let the AI take over once the customer replies.
How do you handle context without burning tokens?
Send a rolling window of recent turns plus a compact summary, not the whole thread. WhatsApp conversations are long-lived and bursty, so replaying every message from six weeks ago is both expensive and worse for accuracy.
My default is the last six to ten turns verbatim, a running summary regenerated every time the thread crosses a turn threshold, and retrieved chunks capped hard. Prompt caching helps if your system prompt is large and stable, which is an argument for keeping tenant-specific facts in the retrieved section rather than baked into the system prompt.
Context rules I enforce in code, not in the prompt:
- Hard token ceiling per request, enforced before the call, with truncation from the oldest turn forward.
- Media messages become a short text description in history, never a re-uploaded blob.
- Retrieved chunks are always tagged with their source so the model can cite a policy line back to the customer.
- Anything the user says that looks like an instruction to change the bot's role is stripped before it hits the prompt.
- Store the raw webhook payload separately from the cleaned turn. You will need it the first time a customer disputes what was said.
# FastAPI webhook: Meta Cloud API -> LLM -> reply
from fastapi import BackgroundTasks, FastAPI, Request
app = FastAPI()
TOOLS = [
{
"name": "lookup_order",
"description": "Fetch order status from the CRM by order number.",
"input_schema": {
"type": "object",
"properties": {"order_number": {"type": "string"}},
"required": ["order_number"],
},
}
]
@app.post("/webhook")
async def inbound(req: Request, tasks: BackgroundTasks):
body = await req.json()
value = body["entry"][0]["changes"][0]["value"]
if "messages" not in value:
return {"ok": True} # delivery status callback, not a message
msg = value["messages"][0]
if seen_before(msg["id"]):
return {"ok": True} # Meta retries; dedupe on message id
tasks.add_task(handle_turn, msg)
return {"ok": True} # answer fast, think later
def handle_turn(msg):
wa_id = msg["from"]
touch_service_window(wa_id)
history = load_window(wa_id, max_turns=8)
reply = run_agent(history + [user_turn(msg)], tools=TOOLS)
send_session_text(wa_id, reply)
save_turn(wa_id, msg, reply)How do you wire tool calls into a CRM?
Define every business action as a typed tool with a strict schema, execute it server-side, and feed the result back to the model as a tool result. The model decides which tool to call; it never decides what the data says.
This is the single biggest quality lever. A model asked to talk about orders will hallucinate an order. A model given a lookup_order tool will either call it or admit it cannot find the record. On ecommerce builds this is the difference between a bot that reduces tickets and one that creates them, which is why I lean on it heavily for ecommerce clients.
The tool set I ship on almost every WhatsApp build, and the guardrail each one needs.
| Tool | What it does | Guardrail |
|---|---|---|
| lookup_order | Reads order status from the CRM by number | Verify the number belongs to this WhatsApp ID before returning anything |
| search_knowledge | Vector search over policy and product docs | Return sources; refuse to answer when top score is below threshold |
| create_ticket | Opens a support ticket with a summary | Idempotency key per conversation so retries do not duplicate |
| book_slot | Writes an appointment to the calendar | Confirm back to the user in a separate message before committing |
| escalate_to_human | Flags the thread and pages the on-duty agent | Always available; never gated behind model confidence |
| update_contact | Writes name, email or preference to the CRM | Allowlist of writable fields; no free-form column access |
Where the CRM is GoHighLevel, HubSpot or a bespoke pipeline, the tool layer is just an authenticated HTTP client with retries and an idempotency key. I usually build it as a thin FastAPI service so the same tools serve WhatsApp, web chat and voice, which is the pattern behind AgentFlow. Keeping that layer transport-agnostic is what makes the queues and workers around it easy to reason about later.
When should the bot hand off to a human?
Hand off on explicit request, on detected frustration or complaint intent, on any money-moving action, and after two or three turns without resolution. Handoff is a feature, not a failure state, and it should be reachable in one message at any point.
The mechanics matter as much as the trigger. When a thread escalates, I set a paused flag on the contact so the webhook stops calling the model entirely, push the last several turns plus a one-paragraph summary into the agent's inbox, and post a single message telling the customer a human is coming and roughly when.
The handoff sequence I implement every time:
- 1Detect the trigger, either from a tool call the model made or from a deterministic rule such as a keyword or a refund amount.
- 2Write ai_paused = true on the contact record. This is a database flag, not prompt instruction, because prompts can be argued with.
- 3Send one acknowledgement message inside the session window, with a realistic response time. Never promise an instant reply.
- 4Post the conversation summary, the customer record and any tool results into the agent console or a Slack channel.
- 5Give the agent a one-click resume that clears the flag and injects a note into history saying a human handled the previous turns.
- 6Log the trigger reason. Handoff reasons are the highest-signal dataset you will have for improving the bot.
How much does an AI chatbot on WhatsApp cost to run?
Two independent bills: Meta's messaging charges and your LLM token spend. Meta moved to per-message pricing, where you are charged when an approved template is delivered, with rates varying by template category and country, while free-form replies inside the customer service window are not charged. Utility templates sent while that window is still open are also free, which is a real argument for resolving in-window rather than reopening later. Check Meta's official pricing page for current rates before you model anything.
LLM cost scales as tokens per turn multiplied by turns per conversation multiplied by conversation volume. The lever most teams ignore is the first factor. Halving your context window halves the bill without touching volume, and usually improves answer quality at the same time.
Where the money actually goes, and the control that moves each line.
| Cost line | Driver | Lever |
|---|---|---|
| Template messages | Category and destination country, per delivered message | Send fewer, better-targeted templates; prefer utility over marketing where the use case allows |
| Session replies | Not charged by Meta inside the window | Design flows so resolution happens in-window rather than via re-engagement |
| LLM input tokens | System prompt plus history plus retrieved chunks | Rolling window, summarisation, prompt caching, smaller top-k |
| LLM output tokens | Reply length | Cap max tokens; WhatsApp rewards short replies anyway |
| Model tier | Which model handles which turn | Route simple intents to a small model, escalate only complex turns |
| Infrastructure | Webhook uptime, database, vector store | One small container plus managed Postgres covers a surprising amount of traffic |
Routing is underrated. A classifier or a small model can resolve greetings, hours and order-status intents for a fraction of the cost of your best model, which then only sees the turns that need it. The full conversation-billing mechanics are broken down in the WhatsApp Business API pricing guide.
What breaks in production?
Duplicates, ordering and silence. Meta retries webhooks it thinks failed, users send three messages in six seconds, and your model occasionally takes twelve seconds to answer a question about opening hours.
The failure modes I now build for on day one:
- Duplicate deliveries: dedupe on the inbound message id in Redis with a short TTL before any processing.
- Message bursts: debounce by a couple of seconds and concatenate the burst into one turn, or the bot answers each fragment separately.
- Out-of-order media: image and document webhooks arrive with a media id you must fetch separately, and that fetch can fail after the text turn is already answered.
- Template rejection: categories get reclassified, and a rejected template silently kills a re-engagement flow. Alert on template status changes.
- Number quality rating: aggressive template sending degrades your quality rating and can throttle throughput. Watch it like an uptime metric.
- Prompt injection through customer messages: never let message content reach a tool argument unvalidated.
If you are still deciding whether the Cloud API is the right surface at all, the Business API versus Business App comparison is the honest version of that decision. And if you would rather have this designed, built and handed over documented, that is what my WhatsApp Business API work and broader AI automation services exist for.
Key takeaways
- An AI chatbot on WhatsApp is a webhook, a conversation store, an LLM call with tools, and a send path back to Meta's Cloud API.
- The 24-hour customer service window is a state machine, not a billing detail: free-form inside, approved templates outside.
- Bind every business action to a typed tool executed server-side so the model chooses actions but never invents data.
- Handoff should be triggered by a database flag, reachable in one message, and logged with its reason for later analysis.
- Cost is Meta's per-template charges plus tokens per turn times volume; trimming context is the cheapest lever you have.
- Deduplicate on inbound message id and debounce bursts, or the bot will answer the same customer several times.
Frequently asked questions
- How much does it cost to build an AI chatbot on WhatsApp?
- Build cost depends on scope, not on WhatsApp. A single-tenant bot with retrieval and two or three CRM tools is a few weeks of engineering; a multi-tenant platform is months. Running cost is Meta's per-template messaging charges plus LLM tokens. Check Meta's official pricing page for current messaging rates.
- Can I use ChatGPT or Claude directly with WhatsApp?
- Not directly. Neither OpenAI nor Anthropic connects to WhatsApp out of the box. You need a server in between that receives Meta's Cloud API webhook, assembles context, calls the model API, and posts the reply to the messages endpoint. That middle service is the actual product.
- What is the difference between a session message and a template message?
- Session messages are free-form and only allowed inside the 24-hour customer service window that opens when a user messages you. Template messages are pre-approved by Meta, can be sent at any time including cold outbound, come in marketing, utility and authentication categories, and are charged per delivered message.
- Can an AI chatbot on WhatsApp send the first message?
- Only through an approved template. Meta's messaging policy requires businesses to have opt-in before messaging a user, and outbound conversations must start with a pre-approved template. Your model can choose which template to send and fill its variables, but it cannot generate the opening text freely.
- Do I need a Business Solution Provider or can I go direct to the Cloud API?
- You can go direct. Meta's Cloud API is publicly available and hosts the messaging infrastructure itself, so a BSP is optional. BSPs like Twilio add a dashboard, support and sometimes billing consolidation at a markup. If you have an engineer who can run a webhook, direct is cheaper and less abstracted.
- How do I stop the chatbot from hallucinating order details?
- Give it tools instead of facts. Define a lookup_order function with a strict schema, execute it server-side against the CRM, and return the result as a tool result. Instruct the model to say it cannot find a record rather than guess, and verify the order belongs to that WhatsApp number.
- Is an AI chatbot on WhatsApp worth it for a small business?
- It is worth it when inbound volume is repetitive and high enough that a person answers the same five questions daily. Below that, a shared inbox with quick replies wins. The break-even is about volume and repetition, not company size, and the honest test is how many tickets are genuinely templated.
- How do I test a WhatsApp chatbot before going live?
- Use a test phone number in the Meta developer app to exercise webhooks without touching production, then run a staging number with real templates submitted separately. Replay saved webhook payloads against your service in CI so parser changes cannot break inbound handling silently.
Sources
Written by Syed Husnain Haider Bukhari
AI engineer, data scientist, and founder of Revolutionary Technologies LLC. Ships production AI agents, automations, and data platforms for teams in the US, UK, and UAE — including AgentFlow, AI Walay, and ProLeads.
Get in touch →Related pages
Want this built instead of researched?
Book a 30-minute scoping call. You get a one-page plan and a fixed-scope quote within 48 hours.
Start a conversation