Skip to content

πŸ“ž Retell AI webhook ingestion + call analytics dashboard + transcript viewer. Next.js 16, Drizzle, Supabase Postgres, shadcn/ui.

Notifications You must be signed in to change notification settings

thiagobardini/voice-ai-analytics

Repository files navigation

Voice AI Analytics

A voice AI webhook receiver with analytics dashboard. Receives webhooks from Retell AI, stores conversation data, and displays analytics.

Demo Video

πŸ‘‰ Watch Demo Video

Click the link above to watch the step-by-step demo of how the platform works.

πŸ“ View on Portfolio


Tech Stack

  • Next.js 16 (App Router, React 19, TypeScript strict mode)
  • Drizzle ORM + Supabase Postgres - Documentation
  • shadcn/ui + Tailwind CSS v4
  • Zod validation - Documentation
  • Retell AI SDK with Conversation Flow

Project Structure

src/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ api/webhooks/retell/route.ts   β†’ Webhook receiver
β”‚   β”œβ”€β”€ dashboard/page.tsx              β†’ Analytics dashboard
β”‚   └── interview/[callId]/page.tsx     β†’ Call detail view
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ dashboard/                      β†’ Dashboard components
β”‚   β”œβ”€β”€ interview/                      β†’ Call detail components
β”‚   └── ui/                             β†’ Reusable UI components
β”œβ”€β”€ db/
β”‚   β”œβ”€β”€ schema.ts                       β†’ Drizzle schema + types
β”‚   └── index.ts                        β†’ DB connection
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ types.ts                        β†’ Centralized type exports
β”‚   β”œβ”€β”€ utils/                          β†’ Utility functions
β”‚   └── validations/retell.ts           β†’ Zod schemas
docs/
β”œβ”€β”€ retell/
β”‚   β”œβ”€β”€ retell-integration.md           β†’ Retell AI integration docs
β”‚   └── retell-agent-config.json        β†’ Agent configuration
└── ngrok-setup.md                      β†’ ngrok local testing docs

Step 1: Setup

npm install
cp .env.example .env

Environment Variables

# Database (Supabase Postgres)
DATABASE_URL="postgres://..."
DATABASE_POSTGRES_URL_NON_POOLING="postgres://..."

# Retell AI
RETELL_API_KEY="your_retell_api_key"

Step 2: Vercel Deployment + Supabase

  1. Connect your GitHub repo to Vercel
  2. Connect Supabase integration in Vercel:
    • Vercel Dashboard β†’ Your Project β†’ Storage β†’ Connect Database
    • Select Supabase β†’ This auto-populates DATABASE_POSTGRES_URL
  3. Add remaining environment variable:
    • RETELL_API_KEY
  4. Deploy

Supabase Dashboard

View data directly in:

  • Supabase Dashboard β†’ Table Editor β†’ interviews table (or your configured table name)

Supabase Free Tier Auto-Pause Prevention

Problem: Supabase Free Tier pauses projects after 7 days of inactivity, causing Tenant or user not found errors.

Solution: Health check cron job keeps database active.

How it works:

  1. Health check endpoint: /api/health (src/app/api/health/route.ts)
    • Simple SELECT 1 query to keep DB connection alive
    • Returns status + timestamp
  2. Vercel cron job: Configured in vercel.json
    • Runs every 6 days (0 0 */6 * *)
    • Prevents auto-pause by keeping project active

Manual recovery (if paused):

  1. Go to Supabase Dashboard
  2. Click "Restore project" button
  3. Wait 1-2 minutes for database to restart
  4. Redeploy in Vercel (to reconnect)

Alternative: Upgrade to Supabase Pro ($25/month) for no auto-pause.


Step 3: Retell Agent Configuration

Create a Retell AI agent using Conversation Flow (not Single Prompt mode):

  1. Go to Retell Dashboard β†’ Agents β†’ Create Agent
  2. Select Conversation Flow as the agent type
  3. Configure the conversation flow with blocks:
    • Greeting block - Welcome message
    • Question blocks - Each conversation question
    • Goodbye block - Thank you message
  4. Enable Extract Dynamic Variables on each question block to capture responses
  5. Register your webhook URL in Agent Settings

See docs/retell/retell-integration.md for detailed configuration.


Step 4: Database Schema

src/db/schema.ts

Simple schema with Drizzle:

  • callId as unique identifier
  • participantId
  • transcript stored as JSON array
  • duration in milliseconds
  • completionStatus
  • timestamps

Drizzle gives us type inference with $inferSelect and $inferInsert types.

npm run db:generate   # Generate migrations
npm run db:push       # Push schema to database
npm run db:studio     # Open Drizzle Studio

Step 5: Zod Validation

src/lib/validations/retell.ts

The Zod schema matches Retell's nested structure:

  • event type at the top level (call_started, call_ended, call_analyzed)
  • call object with all the call data
  • transcript_object as an array of messages

Step 6: Webhook Endpoint

src/app/api/webhooks/retell/route.ts

The webhook endpoint does 4 things:

  1. Validates signature using Retell SDK (Retell.verify()) - real validation, not mock
  2. Parses and validates payload with Zod
  3. Calculates duration from timestamps (end_timestamp - start_timestamp)
  4. Saves to database with Drizzle (check if exists, then insert or update)

Retell Documentation References


Step 7: Dashboard

src/app/dashboard/page.tsx

The dashboard shows:

  • Total calls count
  • Average duration
  • Completion rate
  • Question-by-question analytics - each question, response count, sample answers
  • Recent calls list

Step 8: Call Detail

src/app/interview/[callId]/page.tsx

The detail page shows:

  • Metadata at the top (call ID, duration, status)
  • Complete transcript formatted as conversation between agent and user

Step 9: Development

npm run dev

Local Testing with ngrok

# Terminal 1: Start Next.js
npm run dev

# Terminal 2: Start ngrok
ngrok http 3000

# Register in Retell dashboard:
# https://abc123.ngrok-free.app/api/webhooks/retell

Production Webhook URL

POST https://your-domain.vercel.app/api/webhooks/retell

Vercel Logs

After a webhook is received, check logs in:

  • Vercel Dashboard β†’ Your Project β†’ Logs
  • Look for: βœ… Call saved: [call_id]

Technical Decisions

  1. Drizzle ORM - SQL-like API with great TypeScript support
  2. Real signature validation - Using Retell SDK, not mocking
  3. Zod nested schema - Matches Retell's actual payload structure
  4. Check then insert/update - Check if exists first, then insert or update
  5. Server Components - Dashboard and detail pages fetch data directly
  6. Reusable components - Small, focused components with barrel exports

Trade-offs & Assumptions

Extract Dynamic Variables vs Transcript Parsing

Instead of parsing transcripts with regex/AI to extract answers, this project uses Retell's Extract Dynamic Variables feature. This gives us structured data directly from the conversation flow.

Why: More reliable, no parsing errors, works regardless of how the user phrases their answer.

Trade-off: Requires using Conversation Flow with Blocks instead of Single Prompt mode.

See: docs/retell/retell-integration.md

JSON Column for Extracted Variables

Extracted variables are stored in a JSON column (extracted_variables) rather than separate columns.

Why: Flexible schema - can add new variables without migrations. Easy to extend for different conversation types.

Trade-off: Can't query individual fields with SQL WHERE clauses. For production, consider adding a GIN index or separate columns for frequently queried fields.

Server Components with force-dynamic

Dashboard and interview pages use Server Components with force-dynamic to always fetch fresh data.

Why: Simpler than client-side state management. No stale data issues.

Trade-off: Every page load hits the database. For high traffic, add caching or ISR.

Manual Refresh vs Auto-Polling

Added a Refresh button instead of automatic polling.

Why: Data changes infrequently (when interviews complete). Polling wastes resources.

Trade-off: User must click to see new data. Could add Server-Sent Events for real-time updates in production.

Sequential Participant IDs

Participant IDs are generated as participant-1, participant-2, etc. using COUNT(*).

Trade-off: Theoretical race condition with concurrent requests. Acceptable for MVP.

Production solution: Use database sequences or UUID-based IDs.

Real Signature Validation

The project spec suggested "mock validation," but I implemented real signature validation using the Retell SDK (Retell.verify()).

Why: More secure and production-ready. Same amount of code.

Next.js 16 + React 19

Used Next.js 16 with React 19 instead of 14+ as specified.

Why: Latest stable version with improved performance. App Router API is the same.


Features

  • Webhook receiver for Retell AI call events
  • Analytics dashboard
  • Call detail view with transcript

About

πŸ“ž Retell AI webhook ingestion + call analytics dashboard + transcript viewer. Next.js 16, Drizzle, Supabase Postgres, shadcn/ui.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •