AI AgentsWhatsAppNestJSTypeScript

Build a WhatsApp AI Agent with NestJS and Zavu

Learn how to build an AI-powered WhatsApp agent using NestJS and Zavu's managed AI Gateway. No external API keys required - access GPT-4, Claude, and more through Zavu.

Written by: Jennifer VillalobosReviewed by: Victor VillalobosDecember 23, 202512 min read

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

text
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<string>('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<string>('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

  • Deploy your NestJS app to a server with HTTPS
  • Configure the webhook in your Zavu dashboard
  • Create an AI agent for your sender
  • 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:

  • Go to Settings > AI Providers in your Zavu dashboard
  • Add your API keys
  • 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 for more examples and advanced features.

    Need help? Contact us or join our Discord community for support.

    Follow us on social media

    See it in action

    Book a personalized demo to see how Zavu AI Agents can transform your customer communication.

    Build a WhatsApp AI Agent with NestJS and Zavu | Zavu Blog | Zavu