Build a WhatsApp chatbot that takes food orders through natural conversation, manages menu items from a database, and processes orders with conversation context.
Full example: github.com/VoltAgent/voltagent/tree/main/examples/with-whatsapp
What We're Building
A WhatsApp AI agent that:
- Shows menu items from Supabase
- Takes orders through natural conversation
- Collects delivery addresses
- Tracks order status
- Maintains conversation context with working memory
Setup
Clone the example:
npm create voltagent-app@latest -- --example with-whatsapp
cd with-whatsapp
Create .env:
OPENAI_API_KEY=your_openai_api_key
SUPABASE_URL=your_supabase_project_url
SUPABASE_ANON_KEY=your_supabase_anon_key
WHATSAPP_WEBHOOK_TOKEN=your_webhook_verification_token
WHATSAPP_ACCESS_TOKEN=your_whatsapp_access_token
WHATSAPP_PHONE_NUMBER_ID=your_whatsapp_phone_number_id
Database Schema
Run this SQL in your Supabase dashboard:
-- Menu items table
CREATE TABLE public.menu_items (
id SERIAL PRIMARY KEY,
category VARCHAR(50) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
price NUMERIC(10, 2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Orders table
CREATE TABLE public.orders (
id SERIAL PRIMARY KEY,
customer_phone VARCHAR(20) NOT NULL,
customer_address TEXT NOT NULL,
total_amount NUMERIC(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'preparing',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Order items table
CREATE TABLE public.order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER REFERENCES orders(id) ON DELETE CASCADE,
menu_item_id INTEGER REFERENCES menu_items(id) ON DELETE CASCADE,
quantity INTEGER NOT NULL DEFAULT 1,
price NUMERIC(10, 2) NOT NULL
);
-- Sample data
INSERT INTO menu_items (category, name, description, price) VALUES
('Pizza', 'Margherita', 'Fresh tomatoes, mozzarella, basil', 12.99),
('Pizza', 'Pepperoni', 'Pepperoni, mozzarella, tomato sauce', 14.99),
('Burger', 'Classic Burger', 'Beef patty, lettuce, tomato, onion', 10.99),
('Drinks', 'Coke', 'Coca-Cola 330ml', 2.99);
Core Components
Working Memory Schema
Define conversation state with Zod:
const workingMemorySchema = z.object({
orders: z.array(z.object({
menuItemId: z.number(),
itemName: z.string(),
quantity: z.number(),
price: z.number(),
})).default([]),
deliveryAddress: z.string().default(""),
orderStatus: z.enum(["selecting", "address_needed", "completed"]).default("selecting"),
});
Memory Configuration
const memory = new Memory({
storage: new LibSQLMemoryAdapter({
url: "file:./.voltagent/memory.db",
}),
workingMemory: {
enabled: true,
scope: "conversation",
schema: workingMemorySchema,
},
});
Working memory maintains cart state across messages without asking customers to repeat information.
Agent Setup
const agent = new Agent({
name: "whatsapp-order-agent",
instructions: `You are a WhatsApp ordering agent.
Order Flow:
1. Show menu if orders array is empty
2. When customer orders, keep orderStatus as "selecting"
3. When done ordering, change to "address_needed"
4. After receiving address, change to "completed" and execute createOrder tool
Always be friendly and helpful.`,
model: openai("gpt-4o-mini"),
tools: [listMenuItemsTool, createOrderTool, checkOrderStatusTool],
memory,
});
Tools
List Menu Items:
export const listMenuItemsTool = createTool({
name: "listMenuItems",
description: "Lists all menu items from the database",
parameters: z.object({
limit: z.number().optional().default(100),
}),
execute: async ({ limit }) => {
const { data, error } = await supabase
.from("menu_items")
.select("*")
.limit(limit);
return {
success: !error,
data: data || [],
};
},
});
Create Order:
export const createOrderTool = createTool({
name: "createOrder",
description: "Creates a new order",
parameters: z.object({
items: z.array(z.object({
menuItemId: z.number(),
quantity: z.number(),
price: z.number(),
})),
deliveryAddress: z.string(),
}),
execute: async ({ items, deliveryAddress }, context) => {
const totalAmount = items.reduce((sum, item) =>
sum + item.price * item.quantity, 0);
const { data: orderData } = await supabase
.from("orders")
.insert({
customer_phone: context?.userId,
customer_address: deliveryAddress,
total_amount: totalAmount,
status: "preparing",
})
.select()
.single();
const orderItems = items.map(item => ({
order_id: orderData.id,
menu_item_id: item.menuItemId,
quantity: item.quantity,
price: item.price,
}));
await supabase.from("order_items").insert(orderItems);
return {
success: true,
orderId: orderData.id,
totalAmount,
};
},
});
WhatsApp Webhook
new VoltAgent({
agents: { agent },
server: honoServer({
configureApp: (app) => {
// Verification (GET)
app.get("/webhook/whatsapp", async (c) => {
const mode = c.req.query("hub.mode");
const token = c.req.query("hub.verify_token");
const challenge = c.req.query("hub.challenge");
if (mode === "subscribe" && token === process.env.WHATSAPP_WEBHOOK_TOKEN) {
return c.text(challenge || "", 200);
}
return c.text("Forbidden", 403);
});
// Message handler (POST)
app.post("/webhook/whatsapp", async (c) => {
const body = await c.req.json();
const message = body.entry?.[0]?.changes?.[0]?.value?.messages?.[0];
if (message?.type === "text") {
const response = await agent.generateText(message.text.body, {
userId: message.from,
conversationId: `whatsapp_${message.from}`,
});
await sendWhatsAppMessage(message.from, response.text);
}
return c.json({ status: "processed" }, 200);
});
},
}),
logger,
});
Run the Agent
npm run dev
Visit VoltOps Console to test and debug.
Example Conversation
Customer: Hi
Agent: Welcome! Would you like to see our menu?
Customer: Yes
Agent: Here's our menu:
**Pizza**
" Margherita - $12.99
" Pepperoni - $14.99
**Burgers**
" Classic Burger - $10.99
What would you like to order?
Customer: 2 pepperoni pizzas and a coke
Agent: Added to cart:
- 2x Pepperoni Pizza ($29.98)
- 1x Coke ($2.99)
Total: $32.97
Anything else?
Customer: No thanks
Agent: Please provide your delivery address.
Customer: 123 Main St
Agent: Order #1057 confirmed!
=� 123 Main St
=� $32.97
� 30-45 minutes
Key Features
- TypeScript Tools: Zod-typed tools with automatic inference
- Working Memory: Maintains cart state across messages
- WhatsApp Integration: Webhook handlers for Meta API
- Observability: VoltOps platform traces every interaction
- Database Integration: Supabase for menu and orders





Top comments (0)