Overview

Lexington Capital — Call Intelligence Platform

AI-powered sales call analysis for Lexington Capital Holdings. Sales reps upload audio recordings, and the system transcribes, analyzes, and scores each call — then generates Google Docs reports and emails the results.

What It Does

  • Upload — Sales reps submit call recordings via the dashboard
  • Transcribe — AssemblyAI converts audio to text with speaker diarization
  • Analyze — GPT-4o extracts key insights, objections, commitments
  • Score — GPT-4o generates a structured scorecard (rapport, discovery, closing, etc.)
  • Report — Google Docs report auto-generated and emailed

Tech Stack

  • Frontend — Next.js 16, React 19, Tailwind v4, shadcn/ui
  • Auth — Supabase Auth (invite-only, PKCE flow)
  • Database — Supabase (PostgreSQL)
  • Orchestration — n8n Cloud (smrich.app.n8n.cloud)
  • Transcription — AssemblyAI (Universal-2)
  • AI Analysis — GPT-4o via n8n
  • Hosting — Vercel

Quick Links

Production URL https://audio-analysis-webapp.vercel.app
n8n Instance smrich.app.n8n.cloud
n8n Workflow ID Q0RdDGc5BeWSpplQ
Supabase Project oipmskrmitcntytkrivi (shared AutoAgency project)
Invite-only access. Public signup is disabled in Supabase. New users must be invited by an admin via /api/admin/invite.
Architecture

Data Flow

The full pipeline from audio upload to emailed report. Two parallel paths converge: transcription via AssemblyAI proxy and analysis/scoring via n8n + GPT-4o.

End-to-End Pipeline

UploadDashboard
n8n WebhookOrchestrator
AssemblyAITranscription
GPT-4oAnalysis
GPT-4oScorecard
Google DocsReport
EmailDelivery

Dual Callback Pattern

n8n sends results back to the app in two separate POST requests to /api/callback. Both are HMAC-signed.

1
Callback #1: Transcript + Analysis
Full transcription text with speaker labels, plus GPT-4o analysis (key topics, objections, commitments, action items)
2
Callback #2: Scorecard
Structured scoring across categories (rapport, discovery, objection handling, closing) with recommendations and overall grade
Both callbacks must arrive. The frontend merges data from both POSTs into a single job_results row. If only one arrives, the job shows as incomplete.

Sequence Diagram

// 1. User uploads audio file Dashboard --POST /api/upload--> Next.js API Next.js API --POST webhook--> n8n Cloud // 2. n8n orchestrates the pipeline n8n --submit--> AssemblyAI (transcription) n8n --prompt--> GPT-4o (analysis) n8n --prompt--> GPT-4o (scorecard) n8n --create--> Google Docs (report) n8n --send--> Email (delivery) // 3. n8n sends results back (2 callbacks) n8n --POST /api/callback--> Next.js (transcript + analysis) n8n --POST /api/callback--> Next.js (scorecard) // 4. Frontend polls for status Dashboard --GET /api/executions/[id]--> Next.js
Security

Authentication

Invite-only Supabase Auth with PKCE flow. No public signup. All user-facing routes require a valid JWT.

Auth Architecture

Invite Email/api/admin/invite
Set PasswordSupabase UI
Login/login
PKCE Exchange/api/auth/callback
JWT CookieHttpOnly

User-Facing Routes (JWT)

All dashboard pages and user API routes verify the Supabase JWT from the session cookie.

  • Middleware intercepts all requests
  • Redirects unauthenticated users to /login
  • JWT decoded server-side via @supabase/ssr
  • Auth context provider wraps the entire app

Machine-to-Machine Routes

Non-user routes use different auth mechanisms depending on the caller.

  • n8n callbacks — HMAC signature (SHA-256)
  • Transcription API — API key in header
  • AssemblyAI webhooks — Open (validated by transcription ID)
  • PKCE callback — Open (one-time code exchange)

Auth Flow Layers

Layer Mechanism Where
Middleware Supabase session check → redirect to /login if unauthenticated middleware.ts
API Routes createClient() + getUser() on each request src/app/api/*
Client Side Auth context provider, useAuth() hook src/components/auth-provider.tsx
Admin Service role key for invite-only signup /api/admin/invite
🚨
Public signup MUST be disabled in Supabase Dashboard → Authentication → Settings. The invite endpoint uses the service role key to create users directly.
Security

HMAC Callback Security

All n8n-to-app callbacks are signed with HMAC-SHA256 to prevent unauthorized data injection.

How It Works

n8n signs payloadHMAC-SHA256
X-Callback-SignatureHTTP Header
Server verifiesTiming-safe compare
Accept / Reject200 or 401

Implementation

// n8n side (HTTP Request node, set header): X-Callback-Signature: sha256=HMAC(body, CALLBACK_SECRET) // Server side (/api/callback): 1. Extract `X-Callback-Signature` header 2. Compute HMAC-SHA256 of raw request body using `CALLBACK_SECRET` 3. Compare using timing-safe equality (prevents timing attacks) 4. Reject with 401 if mismatch

Production Mode

  • Signature is required
  • Missing or invalid signature = 401 Unauthorized
  • Uses timingSafeEqual from Node.js crypto
  • CALLBACK_SECRET must be set in env vars

Dev Mode Degradation

  • If CALLBACK_SECRET is not set, signature check is skipped
  • Logs a warning: "HMAC validation skipped"
  • Allows local development without n8n signing
  • Never deploy to production without the secret
The same CALLBACK_SECRET value must be configured in both the Vercel environment variables and the n8n workflow's HTTP Request node. If they don't match, all callbacks will be rejected.
Integration

Transcription Proxy

AssemblyAI integration via an async webhook pattern. Avoids Vercel's 60-second function timeout by using a submit-and-callback architecture.

Why a Proxy?

Problem: Vercel Timeout

Vercel serverless functions timeout after 60 seconds. A 30-minute call takes 2-5 minutes to transcribe. Synchronous transcription is impossible.

Solution: Async Webhooks

Submit the job to AssemblyAI, return immediately, let AssemblyAI call back when done. n8n polls the status endpoint until complete.

Async Flow

POST /api/transcribeSubmit audio URL
AssemblyAIUpload + start job
Return ID~200ms response
AssemblyAIProcessing (async)
POST /api/transcribe/callbackWebhook on completion
Store resultSupabase
n8n pollsGET /api/transcribe/status/[id]
Transcript readyContinue pipeline

Endpoints

Endpoint Method Auth Purpose
/api/transcribe POST API Key Submit audio URL for transcription. Returns { transcriptId }
/api/transcribe/callback POST None* AssemblyAI webhook. Stores completed transcript in Supabase
/api/transcribe/status/[id] GET API Key Poll transcription status. Returns status + result when done

* AssemblyAI callback is validated by checking the transcript ID exists in the database. No external secret is needed.

Cost Savings: 67% Reduction

$0.02
Rev.ai (old)
per minute
$0.0065
AssemblyAI (new)
per minute
67%
Cost Reduction
per transcription
API Reference

API Endpoints

Complete reference for all API routes. Color-coded by authentication type.

Auth Type Legend

JWT Supabase session cookie
HMAC SHA-256 signature header
API Key X-API-Key or Authorization header
None Open endpoint (validated by context)

All Routes

Endpoint Method Auth Purpose
/api/upload POST JWT Submit audio file for analysis. Forwards to n8n webhook.
/api/callback POST HMAC Receive analysis results from n8n. Dual callback pattern (2 POSTs per job).
/api/jobs GET JWT List all completed job results for the authenticated user.
/api/executions/[id] GET JWT Check status of a specific job. Frontend polls this after upload.
/api/transcribe POST API Key Submit audio URL for AssemblyAI transcription. Returns transcript ID.
/api/transcribe/callback POST None* AssemblyAI webhook endpoint. Stores completed transcript.
/api/transcribe/status/[id] GET API Key Poll transcription job status. Returns result when complete.
/api/admin/invite POST JWT Invite a new user by email. Uses Supabase service role key.
/api/auth/callback GET None PKCE code exchange. Supabase redirects here after login/signup.

* Validated by transcript ID existence in database, not by external credentials.

Polling pattern: After uploading, the frontend polls /api/executions/[id] every few seconds until the job status changes to "completed". Both callbacks must arrive for the job to be fully complete.
Configuration

Environment Variables

All 12 environment variables required for the application. Set these in Vercel dashboard and in .env.local for development.

Supabase (5 vars)

Variable Description Where Used
SUPABASE_URL Supabase project URL (server-side) API routes
SUPABASE_ANON_KEY Supabase anonymous/public key (server-side) API routes
NEXT_PUBLIC_SUPABASE_URL Supabase project URL (client-side) Browser auth
NEXT_PUBLIC_SUPABASE_ANON_KEY Supabase anonymous/public key (client-side) Browser auth
SUPABASE_SERVICE_ROLE_KEY Admin key for user invites (bypasses RLS) /api/admin/invite

n8n (3 vars)

Variable Description Where Used
N8N_WEBHOOK_URL n8n webhook trigger URL for audio processing pipeline /api/upload
N8N_API_URL n8n REST API base URL for execution status checks /api/executions/[id]
N8N_API_KEY n8n API key for authenticated REST API calls /api/executions/[id]

Application (4 vars)

Variable Description Where Used
NEXT_PUBLIC_APP_URL Public URL of the app (for PKCE redirect) Auth callback
CALLBACK_SECRET Shared secret for HMAC-SHA256 callback signing /api/callback
ASSEMBLYAI_API_KEY AssemblyAI API key for transcription /api/transcribe
TRANSCRIBE_API_KEY API key for machine-to-machine transcription endpoints /api/transcribe, /api/transcribe/status
🚨
Never commit secrets. All env vars go in .env.local (gitignored) for development, and in the Vercel dashboard for production. The NEXT_PUBLIC_* prefix exposes values to the browser — only use it for non-secret values.
Database

Database Schema

Two tables in Supabase (PostgreSQL). Shared project oipmskrmitcntytkrivi.

job_results

Stores analysis results from n8n callbacks. Each row represents one completed (or in-progress) analysis job.

Column Type Notes
execution_id TEXT Primary key. n8n execution ID.
data JSONB Merged results from both callbacks (transcript, analysis, scorecard).
created_at TIMESTAMPTZ Auto-set on insert. Default: now()

transcription_jobs

Tracks AssemblyAI transcription jobs through their lifecycle. Created on submit, updated on callback.

Column Type Notes
id TEXT Primary key. AssemblyAI transcript ID.
execution_id TEXT Links to the n8n execution (optional, for correlation).
status TEXT Current state: queued, processing, completed, error
audio_url TEXT URL of the submitted audio file.
result JSONB Full transcription result (text + utterances with speaker labels).
error TEXT Error message if transcription failed.
created_at TIMESTAMPTZ When the job was submitted. Default: now()
completed_at TIMESTAMPTZ When AssemblyAI called back with the result.
RLS policies are pending. Row Level Security rules need to be created to restrict access. Currently the service role key bypasses RLS for all operations. User-facing queries should be scoped by auth context once RLS is applied.

Create Tables SQL

-- job_results table CREATE TABLE job_results ( execution_id TEXT PRIMARY KEY, data JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- transcription_jobs table CREATE TABLE transcription_jobs ( id TEXT PRIMARY KEY, execution_id TEXT, status TEXT NOT NULL DEFAULT 'queued', audio_url TEXT, result JSONB, error TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), completed_at TIMESTAMPTZ );
Reference

File Structure

Key files and directories in the project. Next.js 16 App Router layout.

Project Layout

src/ app/ api/ upload/ route.ts # Audio upload + forward to n8n callback/ route.ts # n8n results callback (HMAC) jobs/ route.ts # List all job results executions/ [id]/ route.ts # Check execution status transcribe/ route.ts # Submit transcription callback/ route.ts # AssemblyAI webhook status/ [id]/ route.ts # Poll transcription status admin/ invite/ route.ts # User invite endpoint auth/ callback/ route.ts # PKCE code exchange login/ page.tsx # Login page layout.tsx # Root layout with auth provider page.tsx # Dashboard (upload + results) components/ auth-provider.tsx # Auth context for client components upload-form.tsx # Audio file upload form results-list.tsx # Analysis results display lib/ supabase/ client.ts # Browser Supabase client server.ts # Server-side Supabase client admin.ts # Service role client (admin ops) middleware.ts # Auth middleware (redirect to /login) next.config.ts # Next.js configuration .env.local # Environment variables (gitignored)

Key Patterns

  • App Router — All routes in src/app/api/
  • Server Components — Default for pages
  • Client Components — "use client" for interactive UI
  • Route Handlersroute.ts exports GET/POST

Supabase Client Strategy

  • client.ts — Browser-side, uses anon key, cookie-based auth
  • server.ts — Server-side, uses anon key, reads cookies from request
  • admin.ts — Service role key, bypasses RLS, used only for admin operations
Operations

Deployment Checklist

Step-by-step guide for deploying the platform to production. Follow in order.

Pre-Deployment

  1. Set Vercel environment variables Add all 12 env vars to Vercel project settings. Both Preview and Production environments. Verify
  2. Create database tables Run the CREATE TABLE SQL for job_results and transcription_jobs in Supabase SQL Editor. Pending: transcription_jobs
  3. Apply Row Level Security Write and apply RLS policies to scope data access per user. Pending
  4. Disable public signup Supabase Dashboard → Authentication → Settings → Disable "Enable email signup". Done

Integration Setup

  1. Configure n8n workflow Update workflow Q0RdDGc5BeWSpplQ to add HMAC signing on callback HTTP Request nodes. Set X-Callback-Signature header. Pending
  2. Update n8n transcription step Replace direct Rev.ai calls with AssemblyAI proxy endpoints. Use /api/transcribe to submit, poll /api/transcribe/status/[id] for result. Pending
  3. Set AssemblyAI webhook URL Configure AssemblyAI to call back https://audio-analysis-webapp.vercel.app/api/transcribe/callback. Pending

Go-Live

  1. Deploy to Vercel Push to main branch or trigger manual deploy. Verify build succeeds. Done
  2. Invite first user Use /api/admin/invite to send invitation email. User sets password and logs in. Pending
  3. End-to-end test Upload a test audio file, verify transcription, analysis, scorecard, Google Doc generation, and email delivery all complete.
Deployment is continuous. Every push to main triggers a Vercel build. Preview deployments are created for every PR branch.
Economics

Cost Analysis

Per-call costs and at-scale projections. Based on 12-minute average call duration.

Per-Call Breakdown (12-min average)

Service Rate Per Call % of Total
AssemblyAI Transcription $0.0065/min $0.078 28%
GPT-4o Analysis ~$0.10/call $0.100 36%
GPT-4o Scorecard ~$0.10/call $0.100 36%
Total per call ~$0.28 100%

At Scale: 60 Reps x 6 Hours/Day

1,800
Calls/Day
60 reps x 30 calls
$504
Daily Cost
1,800 x $0.28
$11,088
Monthly Cost
22 working days

Cost Savings vs. Rev.ai

Migrating from Rev.ai ($0.02/min) to AssemblyAI ($0.0065/min) for transcription.

$0.24/min
Rev.ai (old)
$190,080/yr at scale
$0.078/min
AssemblyAI (new)
$113,100/yr at scale
Projected annual savings: ~$76,980 at full scale (60 reps, 6 hrs/day). AssemblyAI also offers superior accuracy: 3.3% WER, 2.9% speaker count error rate.

Infrastructure Costs

Service Tier Cost
Vercel Pro $20/mo (included in existing plan)
Supabase Free (shared project) $0/mo
n8n Cloud Starter $20/mo (shared instance)
Google Docs Workspace Included
Volume discounts available. AssemblyAI offers custom pricing at 100,000+ minutes/month. At full scale, negotiate for ~$0.005/min (additional 23% savings).