--- title: Build a WhatsApp AI Agent with NestJS and Zavu description: Learn how to build an AI-powered WhatsApp agent using NestJS and Zavu's managed AI Gateway. date: 2025-12-23 author: Jennifer Villalobos locale: en source: https://www.zavu.dev/en/blog/whatsapp-ai-agents-nestjs tags: AI Agents, WhatsApp, NestJS, TypeScript --- # Build a WhatsApp AI Agent with NestJS and Zavu In this tutorial, you'll build a production-ready WhatsApp AI agent using NestJS 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 NestJS application that: - Receives incoming WhatsApp messages via webhooks - Verifies webhook signatures for security - Uses Zavu's managed AI agents for intelligent responses - Leverages NestJS's dependency injection and modular architecture - Automatically maintains conversation context ## Prerequisites - Node.js 18+ - A Zavu account with API credentials - Basic 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 NestJS project and install dependencies: Choose your package manager: **Bun (recommended)**: ```bash bun install @zavudev/sdk @nestjs/config dotenv ``` **npm**: ```bash npm install @zavudev/sdk @nestjs/config dotenv ``` **pnpm**: ```bash pnpm add @zavudev/sdk @nestjs/config dotenv ``` Then create the project: ```bash npx @nestjs/cli new whatsapp-agent cd whatsapp-agent ``` ## Project Structure ``` whatsapp-agent/ ├── src/ │ ├── app.module.ts │ ├── main.ts │ ├── webhook/ │ │ ├── webhook.module.ts │ │ ├── webhook.controller.ts │ │ ├── webhook.service.ts │ │ └── dto/ │ │ └── webhook-event.dto.ts │ └── zavu/ │ ├── zavu.module.ts │ └── zavu.service.ts ├── .env └── nest-cli.json ``` ## Environment Configuration Create a `.env` file in your project root: ```bash ZAVUDEV_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. ## Create the Zavu Module Create `src/zavu/zavu.module.ts`: ```typescript import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ZavuService } from './zavu.service'; @Module({ imports: [ConfigModule], providers: [ZavuService], exports: [ZavuService], }) export class ZavuModule {} ``` Create `src/zavu/zavu.service.ts`: ```typescript import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Zavudev from '@zavudev/sdk'; @Injectable() export class ZavuService { private readonly zavu: Zavu; constructor(private configService: ConfigService) { this.zavu = new Zavu({ apiKey: this.configService.get('ZAVUDEV_API_KEY'), }); } getClient(): Zavu { return this.zavu; } async createAgent(senderId: string, name: string, systemPrompt: string, model = 'gpt-4o-mini') { return await this.zavu.senders.agent.create({ senderId, name, provider: 'zavu', model, systemPrompt, contextWindowMessages: 10, includeContactMetadata: true, enabled: true }); } async createKnowledgeBase(senderId: string, name: string, description?: string) { return await this.zavu.senders.agent.knowledgeBases.create({ senderId, name, description }); } async addDocumentToKB(senderId: string, kbId: string, title: string, content: string) { return await this.zavu.senders.agent.knowledgeBases.documents.create({ senderId, kbId, title, content }); } async createTool(senderId: string, name: string, description: string, webhookUrl: string, parameters: any) { return await this.zavu.senders.agent.tools.create({ senderId, name, description, webhookUrl, parameters }); } } ``` ## Create the Webhook Module Create `src/webhook/dto/webhook-event.dto.ts`: ```typescript export class WebhookEventDto { type: string; data: any; timestamp?: string; } ``` Create `src/webhook/webhook.service.ts`: ```typescript import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as crypto from 'crypto'; @Injectable() export class WebhookService { private readonly logger = new Logger(WebhookService.name); constructor(private configService: ConfigService) {} verifyWebhookSignature(payload: Buffer, signature: string): boolean { const secret = this.configService.get('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) ); } async handleWebhookEvent(event: any) { this.logger.log(`Received webhook event: ${event.type}`); if (event.type === 'message.inbound') { const message = event.data; this.logger.log(`Message from ${message.from}: ${message.text}`); } return { status: 'ok' }; } } ``` Create `src/webhook/webhook.controller.ts`: ```typescript import { Controller, Post, Body, Headers, RawBody, HttpStatus, HttpException } from '@nestjs/common'; import { WebhookService } from './webhook.service'; @Controller('webhook') export class WebhookController { constructor(private readonly webhookService: WebhookService) {} @Post() async handleWebhook( @Body() body: any, @Headers('x-zavu-signature') signature: string, @RawBody() rawBody: Buffer, ) { if (!this.webhookService.verifyWebhookSignature(rawBody, signature)) { throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED); } try { return await this.webhookService.handleWebhookEvent(body); } catch (error) { throw new HttpException( 'Internal server error', HttpStatus.INTERNAL_SERVER_ERROR ); } } } ``` Create `src/webhook/webhook.module.ts`: ```typescript import { Module } from '@nestjs/common'; import { WebhookController } from './webhook.controller'; import { WebhookService } from './webhook.service'; import { ZavuModule } from '../zavu/zavu.module'; @Module({ imports: [ZavuModule], controllers: [WebhookController], providers: [WebhookService], }) export class WebhookModule {} ``` ## Update the App Module Update `src/app.module.ts`: ```typescript import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { WebhookModule } from './webhook/webhook.module'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, }), WebhookModule, ], }) export class AppModule {} ``` ## Update main.ts Update `src/main.ts`: ```typescript import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use((req, res, next) => { if (req.path === '/webhook' && req.method === 'POST') { let data = ''; req.setEncoding('utf8'); req.on('data', (chunk) => { data += chunk; }); req.on('end', () => { req.rawBody = Buffer.from(data); next(); }); } else { next(); } }); app.useGlobalPipes(new ValidationPipe()); const PORT = process.env.PORT || 3000; await app.listen(PORT); console.log(`Application is running on: http://localhost:${PORT}`); } bootstrap(); ``` ## Deploy and Configure 1. Deploy your NestJS app to a server with HTTPS 2. Configure the webhook in your Zavu dashboard 3. Create an AI agent for your sender 4. Run the server: ```bash npm run start:prod ``` ## Create a Managed AI Agent ### Option 1: Via Zavu Dashboard Create an agent directly in the **Zavu Dashboard**. ### Option 2: Via NestJS Service Create a controller `src/agent/agent.controller.ts`: ```typescript import { Controller, Post, Body } from '@nestjs/common'; import { ZavuService } from '../zavu/zavu.service'; @Controller('agent') export class AgentController { constructor(private readonly zavuService: ZavuService) {} @Post('create') async createAgent(@Body() body: { senderId: string; name: string; systemPrompt: string; model?: string; }) { const agent = await this.zavuService.createAgent( body.senderId, body.name, body.systemPrompt, body.model ); return { agent }; } } ``` ## Knowledge Bases ```typescript // In zavu.service.ts async createKnowledgeBase(senderId: string, name: string, description?: string) { const response = await this.zavu.senders.agent.knowledgeBases.create({ senderId, name, description }); return response.knowledgeBase; } async addDocumentToKB(senderId: string, kbId: string, title: string, content: string) { const response = await this.zavu.senders.agent.knowledgeBases.documents.create({ senderId, kbId, title, content }); return response.document; } ``` ## Custom Tools ```typescript // In zavu.service.ts async createTool( senderId: string, name: string, description: string, webhookUrl: string, parameters: any ) { const response = await this.zavu.senders.agent.tools.create({ senderId, name, description, webhookUrl, parameters }); return response.tool; } ``` Create a tool endpoint controller: ```typescript import { Controller, Post, Body } from '@nestjs/common'; @Controller('tools') export class ToolsController { @Post('order-status') async getOrderStatus(@Body() body: { order_id: string }) { const orderStatus = await this.getOrderFromDatabase(body.order_id); return { success: true, data: { order_id: body.order_id, status: orderStatus.status, estimated_delivery: orderStatus.delivery_date, tracking_url: orderStatus.tracking_url } }; } private async getOrderFromDatabase(orderId: string) { return { status: 'shipped', delivery_date: '2024-01-20', tracking_url: 'https://tracker.example.com/ORD-12345' }; } } ``` ## Using Your Own AI Credentials (Optional) If you prefer to use your own AI provider credentials: 1. Go to **Settings** > **AI Providers** in your Zavu dashboard 2. Add your API keys 3. When creating an agent, use `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 Check out the [Zavu documentation](https://docs.zavu.dev) for more examples and advanced features.