Build a WhatsApp AI Agent with Next.js and Zavu
In this tutorial, you'll build a production-ready WhatsApp AI agent using Next.js App Router 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 Next.js application that:- Receives incoming WhatsApp messages via Route Handlers
- Verifies webhook signatures for security
- Uses Zavu's managed AI agents for intelligent responses
- Leverages Next.js App Router and Server Actions
- Automatically maintains conversation context
Prerequisites
- Node.js 18+
- A Zavu account with API credentials
- Basic React and TypeScript knowledge
Installation
Create a new Next.js project or add to an existing one:
Choose your package manager:
Bun (recommended):npm:bashbun install @zavudev/sdk
pnpm:bashnpm install @zavudev/sdk
bashpnpm add @zavudev/sdk
Then create the project:
When prompted, select:bashnpx create-next-app@latest whatsapp-agent cd whatsapp-agent
- TypeScript: Yes
- ESLint: Yes
- App Router: Yes
- Src directory: Yes
- Import alias: @/*
Project Structure
textwhatsapp-agent/ ├── src/ │ ├── app/ │ │ ├── api/ │ │ │ └── webhook/ │ │ │ └── route.ts │ │ └── page.tsx │ └── lib/ │ └── zavu.ts ├── .env.local └── package.json
Environment Configuration
Create a .env.local file in your project root:
bashZAVUDEV_API_KEY=your_zavu_api_key ZAVU_WEBHOOK_SECRET=your_webhook_secret
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/lib/zavu.ts:
typescriptimport Zavudev from '@zavudev/sdk'; export const zavu = new Zavu({ apiKey: process.env.ZAVUDEV_API_KEY, });
Create the Webhook Route Handler
Create src/app/api/webhook/route.ts:
typescriptimport { NextRequest, NextResponse } from 'next/server'; import crypto from 'crypto'; import { zavu } from '@/lib/zavu'; function verifyWebhookSignature(payload: string, signature: string): boolean { const secret = process.env.ZAVU_WEBHOOK_SECRET; if (!signature''; if (!verifyWebhookSignature(rawBody, signature)) { console.error('Invalid webhook signature'); return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const payload = JSON.parse(rawBody); const eventType = payload.type; console.log(
!secret) { return false; } const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from( sha256=${expected}), Buffer.from(signature) ); } export async function POST(request: NextRequest) { try { const rawBody = await request.text(); const signature = request.headers.get('x-zavu-signature')Received webhook event: ${eventType}); if (eventType === 'message.inbound') { const message = payload.data; console.log(Message from ${message.from}: ${message.text}); } return NextResponse.json({ status: 'ok' }); } catch (error) { console.error('Webhook error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } }
Deploy and Configure
https://yourdomain.com/api/webhook - Enable events: message.inbound - Copy the webhook secret to your .env.local filebashnpm run build npm start
Testing Locally
For local testing, use ngrok:
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 Server Actions
Create a server action at src/app/actions/agent.ts:
typescript'use server'; import { zavu } from '@/lib/zavu'; import { revalidatePath } from 'next/cache'; export async function createAgent(formData: FormData) { const senderId = formData.get('senderId') as string; const name = formData.get('name') as string; const systemPrompt = formData.get('systemPrompt') as string; const response = await zavu.senders.agent.create({ senderId, name, provider: 'zavu', model: 'gpt-4o-mini', systemPrompt, contextWindowMessages: 10, includeContactMetadata: true, enabled: true }); revalidatePath('/dashboard'); return response.agent; } export async function getAgentStats(senderId: string) { const response = await zavu.senders.agent.stats(senderId); return response; }
Knowledge Bases
Create a server action for knowledge bases at src/app/actions/knowledge-base.ts:
typescript'use server'; import { zavu } from '@/lib/zavu'; export async function createKnowledgeBase(formData: FormData) { const senderId = formData.get('senderId') as string; const name = formData.get('name') as string; const description = formData.get('description') as string; const response = await zavu.senders.agent.knowledgeBases.create({ senderId, name, description }); return response.knowledgeBase; } export async function addDocument(formData: FormData) { const senderId = formData.get('senderId') as string; const kbId = formData.get('kbId') as string; const title = formData.get('title') as string; const content = formData.get('content') as string; const response = await zavu.senders.agent.knowledgeBases.documents.create({ senderId, kbId, title, content }); return response.document; }
Custom Tools
Create a tool endpoint at src/app/api/tools/order-status/route.ts:
typescriptimport { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { try { const body = await request.json(); const { order_id } = body; const orderStatus = await getOrderFromDatabase(order_id); return NextResponse.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 NextResponse.json( { success: false, error: 'Internal server error' }, { status: 500 } ); } } async function getOrderFromDatabase(orderId: string) { return { status: 'shipped', delivery_date: '2024-01-20', tracking_url: 'https://tracker.example.com/ORD-12345' }; }
Create the tool via server action:
typescript// src/app/actions/tools.ts 'use server'; import { zavu } from '@/lib/zavu'; export async function createOrderStatusTool(formData: FormData) { const senderId = formData.get('senderId') as string; const webhookUrl =https://${process.env.VERCEL_URL || 'localhost:3000'}/api/tools/order-status; const response = await zavu.senders.agent.tools.create({ senderId, name: 'get_order_status', description: 'Get the current status of a customer order', webhookUrl, parameters: { type: 'object', properties: { order_id: { type: 'string', description: 'The order ID to look up' } }, required: ['order_id'] } }); return response.tool; }
Using Your Own AI Credentials (Optional)
If you prefer to use your own AI provider credentials:
provider: "openai" instead of provider: "zavu"This gives you full control over billing while still benefiting from Zavu's unified API.
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 a dashboard UI to manage your agents