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
No external AI API keys required! Zavu's AI Gateway provides access to all major AI models through a single integration.
Installation
Create a new project and install dependencies:
Choose your package manager:
Bun (recommended):
terminalbun install @zavudev/sdk fastify @fastify/env dotenv
npm:
terminalnpm install @zavudev/sdk fastify @fastify/env dotenv
pnpm:
terminalpnpm add @zavudev/sdk fastify @fastify/env dotenv
Then create the project:
terminalmkdir whatsapp-agent && cd whatsapp-agent npm init -y
Project Structure
whatsapp-agent/
├── src/
│ ├── app.js
│ ├── routes/
│ │ └── webhook.js
│ └── zavu.js
├── .env
└── package.jsonEnvironment Configuration
Create a .env file in your project root:
terminalZAVUDEV_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
You can configure which model to use directly in your Zavu dashboard, and optionally add your own API keys if you prefer to use your own credentials for billing purposes.
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(); } catch (err) { fastify.log.error(err); process.exit(1); } }; start();Server running on port${PORT}
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(), 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(sha256=${expected}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 fileterminalnpm start
Testing Locally with ngrok
For local development, use ngrok to expose your server:
terminalnpx 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
Check out the Zavu documentation for more examples and advanced features.