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) |
/api/admin/invite.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
Dual Callback Pattern
n8n sends results back to the app in two separate POST requests to /api/callback. Both are HMAC-signed.
job_results row. If only one arrives, the job shows as incomplete.Sequence Diagram
Authentication
Invite-only Supabase Auth with PKCE flow. No public signup. All user-facing routes require a valid JWT.
Auth Architecture
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 |
HMAC Callback Security
All n8n-to-app callbacks are signed with HMAC-SHA256 to prevent unauthorized data injection.
How It Works
Implementation
Production Mode
- Signature is required
- Missing or invalid signature = 401 Unauthorized
- Uses
timingSafeEqualfrom Node.js crypto CALLBACK_SECRETmust be set in env vars
Dev Mode Degradation
- If
CALLBACK_SECRETis 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
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.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
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
API Endpoints
Complete reference for all API routes. Color-coded by authentication type.
Auth Type Legend
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.
/api/executions/[id] every few seconds until the job status changes to "completed". Both callbacks must arrive for the job to be fully complete.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 |
.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 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. |
Create Tables SQL
File Structure
Key files and directories in the project. Next.js 16 App Router layout.
Project Layout
Key Patterns
- App Router — All routes in
src/app/api/ - Server Components — Default for pages
- Client Components — "use client" for interactive UI
- Route Handlers —
route.tsexportsGET/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
Deployment Checklist
Step-by-step guide for deploying the platform to production. Follow in order.
Pre-Deployment
-
Set Vercel environment variables Add all 12 env vars to Vercel project settings. Both Preview and Production environments. Verify
-
Create database tables Run the
CREATE TABLESQL forjob_resultsandtranscription_jobsin Supabase SQL Editor. Pending: transcription_jobs -
Apply Row Level Security Write and apply RLS policies to scope data access per user. Pending
-
Disable public signup Supabase Dashboard → Authentication → Settings → Disable "Enable email signup". Done
Integration Setup
-
Configure n8n workflow Update workflow
Q0RdDGc5BeWSpplQto add HMAC signing on callback HTTP Request nodes. SetX-Callback-Signatureheader. Pending -
Update n8n transcription step Replace direct Rev.ai calls with AssemblyAI proxy endpoints. Use
/api/transcribeto submit, poll/api/transcribe/status/[id]for result. Pending -
Set AssemblyAI webhook URL Configure AssemblyAI to call back
https://audio-analysis-webapp.vercel.app/api/transcribe/callback. Pending
Go-Live
-
Deploy to Vercel Push to main branch or trigger manual deploy. Verify build succeeds. Done
-
Invite first user Use
/api/admin/inviteto send invitation email. User sets password and logs in. Pending -
End-to-end test Upload a test audio file, verify transcription, analysis, scorecard, Google Doc generation, and email delivery all complete.
main triggers a Vercel build. Preview deployments are created for every PR branch.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
Cost Savings vs. Rev.ai
Migrating from Rev.ai ($0.02/min) to AssemblyAI ($0.0065/min) for transcription.
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 |