Build a WhatsApp AI Agent with Fastify and Zavu
In this tutorial, you'll build a production-ready WhatsApp AI agent using Fastify 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
A Fastify application that:- Receives incoming WhatsApp messages via webhooks
- Verifies webhook signatures for security
- Uses Zavu's managed AI agents for intelligent responses
- Leverages Fastify's built-in schema validation
- 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 fastify @fastify/env dotenv
pnpm:bashnpm install @zavudev/sdk fastify @fastify/env dotenv
bashpnpm add @zavudev/sdk fastify @fastify/env dotenv
Then create the project:
bashmkdir whatsapp-agent && cd whatsapp-agent npm init -y
Project Structure
textwhatsapp-agent/ ├── src/ │ ├── app.js │ ├── routes/ │ │ └── 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 the Fastify App
Create src/app.js:
javascriptimport Fastify from 'fastify'; import dotenv from 'dotenv'; import { webhookRoutes } from './routes/webhook.js'; import { zavu } from './zavu.js'; dotenv.config(); const fastify = Fastify({ logger: true, }); // Register routes fastify.register(webhookRoutes, { zavu }); // Health check fastify.get('/health', async () => { return { status: 'healthy' }; }); // Start server const start = async () => { try { const PORT = process.env.PORT || 3000; await fastify.listen({ port: PORT, host: '0.0.0.0' }); console.log(Server running on port ${PORT}); } catch (err) { fastify.log.error(err); process.exit(1); } }; start();
Create the Webhook Route
Create src/routes/webhook.js:
javascriptimport crypto from 'crypto'; export async function webhookRoutes(fastify, options) { const { zavu } = options; // Schema for webhook validation const webhookSchema = { headers: { type: 'object', properties: { 'x-zavu-signature': { type: 'string' } }, required: ['x-zavu-signature'] }, body: { type: 'object', properties: { type: { type: 'string' }, data: { type: 'object' } }, required: ['type'] } }; // Helper function to verify webhook signature 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) ); } // Webhook endpoint fastify.post('/webhook', { schema: webhookSchema, config: { rawBody: true // Keep raw body for signature verification } }, async (request, reply) => { const signature = request.headers['x-zavu-signature']; const rawBody = request.raw.body; // Verify webhook signature if (!verifyWebhookSignature(rawBody, signature)) { fastify.log.warn('Invalid webhook signature'); return reply.status(401).send({ error: 'Unauthorized' }); } try { const payload = request.body; const eventType = payload.type; fastify.log.info(Received webhook event: ${eventType}); // Handle different event types if (eventType === 'message.inbound') { const message = payload.data; fastify.log.info(Message from ${message.from}: ${message.text}); } return { status: 'ok' }; } catch (error) { fastify.log.error('Webhook error:', error); return reply.status(500).send({ error: 'Internal server error' }); } }); }
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.
Create a Managed AI Agent
Option 1: Via Zavu Dashboard
Option 2: Via JavaScript SDK
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', model: model, systemPrompt: systemPrompt, contextWindowMessages: 10, includeContactMetadata: true, enabled: true }); return response.agent; } // Usage 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});
Knowledge Bases
Create RAG-powered responses by uploading documents.
javascriptimport { zavu } from './zavu.js'; // Create knowledge base const kb = await zavu.senders.agent.knowledgeBases.create({ senderId: 'sender_abc123', name: 'Product FAQ', description: 'Frequently asked questions about our products' }); // Add documents await zavu.senders.agent.knowledgeBases.documents.create({ senderId: 'sender_abc123', kbId: kb.id, title: 'Return Policy', content: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}); console.log(Knowledge base created: ${kb.id});
Custom Tools
Connect external APIs to extend your agent's capabilities.
javascriptimport { zavu } from './zavu.js'; // Create a tool const tool = await zavu.senders.agent.tools.create({ senderId: 'sender_abc123', name: 'get_order_status', description: 'Get the current status of a customer order', webhookUrl: 'https://yourdomain.com/api/tools/order-status', webhookSecret: 'your_tool_webhook_secret', parameters: { 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 the tool endpoint with Fastify:
javascript// In your routes file fastify.post('/api/tools/order-status', { schema: { body: { type: 'object', properties: { order_id: { type: 'string' } }, required: ['order_id'] } } }, async (request, reply) => { const { order_id } = request.body; // Call your internal API/service const orderStatus = await getOrderFromDatabase(order_id); return { success: true, data: { order_id: order_id, status: orderStatus.status, estimated_delivery: orderStatus.delivery_date, tracking_url: orderStatus.tracking_url } }; });
Using Your Own AI Credentials (Optional)
If you prefer to use your own AI provider credentials:
provider: "openai" instead of provider: "zavu"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