Build a WhatsApp AI Agent with Express and Zavu
In this tutorial, you'll build a production-ready WhatsApp AI agent using Express.js and Zavu. The best part? You don't need to manage any external AI API keys - Zavu's AI Gateway gives you access to all top-tier AI models (GPT-4, Claude, Gemini, Mistral, and more) directly from your Zavu dashboard.
What We're Building
An Express application that:- Receives incoming WhatsApp messages via webhooks
- Verifies webhook signatures for security
- Uses Zavu's managed AI agents for intelligent responses
- Automatically maintains conversation context
Prerequisites
- Node.js 18+
- A Zavu account with API credentials
- Basic JavaScript/TypeScript knowledge
Installation
Create a new project and install dependencies:
Choose your package manager:
Bun (recommended):npm:bashbun install @zavudev/sdk express dotenv
pnpm:bashnpm install @zavudev/sdk express dotenv
bashpnpm add @zavudev/sdk express dotenv
Then create the project:
bashmkdir whatsapp-agent && cd whatsapp-agent npm init -y
Project Structure
textwhatsapp-agent/ ├── src/ │ ├── app.js │ ├── webhook.js │ └── zavu.js ├── .env └── package.json
Environment Configuration
Create a .env file in your project root:
bashZAVUDEV_API_KEY=your_zavu_api_key ZAVU_WEBHOOK_SECRET=your_webhook_secret PORT=3000
That's it! No OpenAI, Anthropic, or other AI provider keys needed.
How Zavu AI Gateway Works
Zavu provides a unified AI Gateway that gives you access to all top-tier AI models without managing individual API keys:
- GPT-4o, GPT-4o-mini - OpenAI's latest models
- Claude 3.5 Sonnet, Claude 3 Opus - Anthropic's models
- Gemini Pro - Google's AI models
- Mistral Large - Mistral AI's models
Initialize the Zavu Client
Create src/zavu.js:
javascriptimport Zavudev from '@zavudev/sdk'; export const zavu = new Zavu({ apiKey: process.env.ZAVUDEV_API_KEY, });
Create a Managed AI Agent
The easiest way to add AI capabilities is through Zavu's managed agents. You have two options:
Option 1: Via Zavu Dashboard
Create an agent for your sender directly in the Zavu Dashboard:
With the zavu provider, all AI processing is handled by Zavu's managed gateway. Your agent will automatically respond to incoming WhatsApp messages without any additional code!
Option 2: Via JavaScript SDK
You can also create and configure AI agents programmatically using the @zavudev/sdk package.
javascriptimport { zavu } from './zavu.js'; async function createAIAgent(senderId, name, systemPrompt, model = 'gpt-4o-mini') { const response = await zavu.senders.agent.create({ senderId: senderId, name: name, provider: 'zavu', // Use Zavu's AI Gateway (no external API keys needed) model: model, systemPrompt: systemPrompt, contextWindowMessages: 10, // Include last 10 messages for context includeContactMetadata: true, // Include contact info in context enabled: true }); return response.agent; }
Now you can create an agent from your Express code:
Note: When usingjavascriptimport { createAIAgent } from './zavu.js'; const agent = await createAIAgent( 'sender_abc123', 'Customer Support Bot',You are a helpful customer support assistant for Zavu. Rules: - Be friendly and concise - Help with WhatsApp, SMS, and email messaging questions - If unsure, offer to connect with a human agent - Respond in the same language as the customer); console.log(Agent created: ${agent.id});
provider: "zavu", Zavu's AI Gateway handles all AI model access. No external API keys needed!Available AI Models
When creating an agent, you can choose from these models:
| Provider | Models |
|---|
| OpenAI | gpt-4o, gpt-4o-mini, gpt-4-turbo |
|---|---|
| Anthropic | claude-3-5-sonnet, claude-3-opus, claude-3-haiku |
| gemini-pro, gemini-1.5-pro | |
| Mistral | mistral-large, mistral-medium |
Webhook Signature Verification
Create src/webhook.js:
javascriptimport crypto from 'crypto'; export function verifyWebhookSignature(payload, signature) { const secret = process.env.ZAVU_WEBHOOK_SECRET; if (!signature || !secret) { return false; } const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(sha256=${expected}), Buffer.from(signature) ); }
Create the Webhook Route
Create src/webhook.js:
javascriptimport { verifyWebhookSignature } from './webhook.js'; export async function handleWebhook(req, res) { const signature = req.headers['x-zavu-signature']; // Verify webhook signature if (!verifyWebhookSignature(req.rawBody, signature)) { console.warn('Invalid webhook signature'); return res.status(401).json({ error: 'Unauthorized' }); } try { const payload = req.body; const eventType = payload.type; console.log(Received webhook event: ${eventType}); // Handle different event types if (eventType === 'message.inbound') { const message = payload.data; console.log(Message from ${message.from}: ${message.text}); } return res.json({ status: 'ok' }); } catch (error) { console.error('Webhook error:', error); return res.status(500).json({ error: 'Internal server error' }); } }
Create the Express App
Create src/app.js:
javascriptimport express from 'express'; import dotenv from 'dotenv'; import { handleWebhook } from './webhook.js'; dotenv.config(); const app = express(); const PORT = process.env.PORT || 3000; // Middleware to capture raw body for signature verification app.use('/webhook', express.raw({ type: 'application/json' })); // Parse JSON for other routes app.use(express.json()); // Webhook endpoint app.post('/webhook', handleWebhook); // Health check app.get('/health', (req, res) => { res.json({ status: 'healthy' }); }); app.listen(PORT, () => { console.log(Server running on port ${PORT}); });
Update package.json
Add a start script to package.json:
json{ "type": "module", "scripts": { "start": "node src/app.js", "dev": "node --watch src/app.js" } }
Deploy and Configure
https://yourdomain.com/webhook - Enable events: message.inbound - Copy the webhook secret to your .env filebashnpm start
Testing Locally with ngrok
For local development, use ngrok to expose your server:
bashnpx ngrok http 3000
Copy the HTTPS URL and configure it in your Zavu webhook settings.
Managed Agent Features
Zavu's AI agents come with powerful built-in features, all configurable through the dashboard:
Knowledge Bases
Create RAG-powered responses by uploading documents to give your agent domain-specific knowledge.
#### Via Dashboard
#### Via JavaScript SDK
javascriptimport { zavu } from './zavu.js'; // Create a knowledge base async function createKnowledgeBase(senderId, name, description) { const response = await zavu.senders.agent.knowledgeBases.create({ senderId: senderId, name: name, description: description }); return response.knowledgeBase; } // Add a document to a knowledge base async function addDocumentToKB(senderId, kbId, title, content) { const response = await zavu.senders.agent.knowledgeBases.documents.create({ senderId: senderId, kbId: kbId, title: title, content: content }); return response.document; }
Example usage:
javascript// Create a knowledge base const kb = await createKnowledgeBase( 'sender_abc123', 'Product FAQ', 'Frequently asked questions about our products' ); // Add documents to the knowledge base await addDocumentToKB( 'sender_abc123', kb.id, 'Return Policy',Our return policy allows returns within 30 days of purchase. Items must be: - In original condition - With receipt or proof of purchase - Not used or damaged);
Custom Tools
Connect external APIs to extend your agent's capabilities by creating tools that the AI can call.
#### Via Dashboard
#### Via JavaScript SDK
javascriptimport { zavu } from './zavu.js'; // Create a tool async function createTool(senderId, name, description, webhookUrl, parameters) { const response = await zavu.senders.agent.tools.create({ senderId: senderId, name: name, description: description, webhookUrl: webhookUrl, parameters: parameters }); return response.tool; }
Example usage:
javascript// Create a tool to check order status const tool = await createTool( 'sender_abc123', 'get_order_status', 'Get the current status of a customer order', 'https://yourdomain.com/api/tools/order-status', { type: 'object', properties: { order_id: { type: 'string', description: 'The order ID to look up' } }, required: ['order_id'] } ); console.log(Tool created: ${tool.id});
Then create an endpoint to handle tool invocations:
javascriptimport crypto from 'crypto'; // Tool webhook endpoint app.post('/api/tools/order-status', express.raw({ type: 'application/json' }), async (req, res) => { // Verify tool webhook signature const signature = req.headers['x-zavu-tool-signature']; const secret = process.env.ZAVU_TOOL_WEBHOOK_SECRET; if (signature && secret) { const expected = crypto .createHmac('sha256', secret) .update(req.body) .digest('hex'); if (!crypto.timingSafeEqual( Buffer.from(sha256=${expected}), Buffer.from(signature) )) { return res.status(401).json({ error: 'Unauthorized' }); } } try { const payload = JSON.parse(req.body); const { order_id } = payload; // Call your internal API/service const orderStatus = await getOrderFromDatabase(order_id); return res.json({ success: true, data: { order_id: order_id, status: orderStatus.status, estimated_delivery: orderStatus.delivery_date, tracking_url: orderStatus.tracking_url } }); } catch (error) { console.error('Tool error:', error); return res.status(500).json({ success: false, error: error.message }); } });
Now when a customer asks "Where's my order ORD-12345?", the AI will automatically call your tool with the order ID and provide the response.
Analytics
Track token usage, costs, and performance metrics directly in your Zavu dashboard under the Analytics section.
Using Your Own AI Credentials (Optional)
If you prefer to use your own AI provider credentials for billing purposes, you can configure them in your Zavu dashboard:
provider: "openai" (or your preferred provider) instead of provider: "zavu"This gives you full control over billing while still benefiting from Zavu's unified API and conversation management.
Custom Webhook Logic (Advanced)
If you need custom processing alongside the AI agent, you can add logic in your webhook:
javascriptexport async function handleWebhook(req, res) { const signature = req.headers['x-zavu-signature']; if (!verifyWebhookSignature(req.rawBody, signature)) { return res.status(401).json({ error: 'Unauthorized' }); } try { const payload = req.body; if (payload.type === 'message.inbound') { const message = payload.data; const { from, text } = message; console.log(Message from ${from}: ${text}); // Custom logic if (text.toLowerCase() === 'human') { await zavu.messages.send({ to: from, text: 'Connecting you with a human agent...' }); } } return res.json({ status: 'ok' }); } catch (error) { console.error('Webhook error:', error); return res.status(500).json({ error: 'Internal server error' }); } }
Next Steps
- Explore the AI Agents dashboard to monitor conversations
- Add knowledge bases for domain-specific responses
- Create custom tools to integrate with your backend systems
- Build conversation flows for specific use cases
- Add support for media messages (images, documents)