AI/ML14 min read · 8 April 2026
Building an Agentic Booking System with LangGraph & Twilio WhatsApp
A deep dive into BookingForge AI — a multi-tenant agentic booking SaaS where a LangGraph agent handles the entire booking flow over WhatsApp, from intent detection to slot confirmation.
LangGraphTwilioWhatsAppFastAPIAgentsMulti-tenant
Why WhatsApp for Bookings?
Most UK small businesses still take bookings by phone or email. WhatsApp has 2 billion users — building a booking agent that lives in WhatsApp means zero friction for customers. No app download, no account creation. Just message and book.
The Architecture
Customer WhatsApp → Twilio → FastAPI Webhook → LangGraph Agent → Supabase
↓
Twilio → WhatsApp Reply
LangGraph Agent Design
The booking agent uses a state machine with 5 nodes:
python
from langgraph.graph import StateGraph, END
from typing import TypedDict, Optional
class BookingState(TypedDict):
phone: str
tenant_id: str
messages: list
intent: Optional[str]
service: Optional[str]
date: Optional[str]
slot_id: Optional[int]
confirmed: bool
def build_booking_graph():
graph = StateGraph(BookingState)
graph.add_node("detect_intent", detect_intent_node)
graph.add_node("collect_service", collect_service_node)
graph.add_node("show_slots", show_slots_node)
graph.add_node("confirm_booking", confirm_booking_node)
graph.add_node("handle_cancel", handle_cancel_node)
graph.set_entry_point("detect_intent")
graph.add_conditional_edges("detect_intent", route_intent, {
"book": "collect_service",
"cancel": "handle_cancel",
"unknown": END,
})
graph.add_edge("collect_service", "show_slots")
graph.add_edge("show_slots", "confirm_booking")
graph.add_edge("confirm_booking", END)
return graph.compile()
Intent Detection Node
python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
async def detect_intent_node(state: BookingState) -> BookingState:
last_msg = state["messages"][-1]
result = await llm.ainvoke([
{"role": "system", "content": """
Classify the user's intent as one of: book, cancel, unknown.
Reply with just the intent word.
"""},
{"role": "user", "content": last_msg}
])
state["intent"] = result.content.strip().lower()
return state
Twilio WhatsApp Webhook
python
from fastapi import FastAPI, Form
from twilio.rest import Client
app = FastAPI()
twilio_client = Client(TWILIO_SID, TWILIO_TOKEN)
@app.post("/webhook/whatsapp")
async def whatsapp_webhook(
From: str = Form(...),
Body: str = Form(...),
To: str = Form(...),
):
phone = From.replace("whatsapp:", "")
tenant_id = get_tenant_from_number(To)
# Get or create conversation state
state = await get_conversation_state(phone, tenant_id)
state["messages"].append(Body)
# Run the agent
agent = build_booking_graph()
new_state = await agent.ainvoke(state)
# Save state for next message
await save_conversation_state(phone, tenant_id, new_state)
# Send reply
reply = generate_reply(new_state)
twilio_client.messages.create(
from_=To,
to=From,
body=reply
)
return {"status": "ok"}
Multi-Tenancy
Each business gets their own WhatsApp number via Twilio. The To field in the webhook identifies which tenant the message is for:
python
def get_tenant_from_number(whatsapp_number: str) -> str:
number = whatsapp_number.replace("whatsapp:", "")
tenant = db.query(Tenant).filter(
Tenant.whatsapp_number == number
).first()
return tenant.id if tenant else None
Conversation State Persistence
Between messages, state is stored in Redis:
python
import redis
import json
redis_client = redis.from_url(REDIS_URL)
async def get_conversation_state(phone: str, tenant_id: str) -> BookingState:
key = f"conv:{tenant_id}:{phone}"
data = redis_client.get(key)
if data:
return json.loads(data)
return BookingState(phone=phone, tenant_id=tenant_id, messages=[], confirmed=False)
async def save_conversation_state(phone: str, tenant_id: str, state: BookingState):
key = f"conv:{tenant_id}:{phone}"
redis_client.setex(key, 3600, json.dumps(state)) # 1hr TTL
Example Conversation
Customer: Hi, I want to book a canopy clean
Agent: Hi! I'd love to help you book. What type of service do you need?
1. Basic Clean (£89)
2. Commercial Package (£149)
3. Deep Clean Pro (£249)
Customer: 2
Agent: Great choice! Here are available slots this week:
• Mon 14 Apr - 9:00 AM
• Tue 15 Apr - 2:00 PM
• Wed 16 Apr - 11:00 AM
Reply with your preferred slot number.
Customer: 1
Agent: Perfect! Confirming your booking:
Service: Commercial Package (£149)
Date: Monday 14 April, 9:00 AM
Reply YES to confirm or NO to cancel.
Customer: YES
Agent: ✅ Booking confirmed! Reference: CC-2026-0042
You'll receive a reminder 24 hours before your appointment.
BookingForge AI is currently in active development — the WhatsApp agent is live in testing with 2 pilot businesses.