1→# GEMINI + TELNYX AI VOICE SYSTEM 2→## Complete Implementation Guide for AgileAdapt 3→ 4→**Purpose:** Replace $97/client/month GHL Voice AI with $0.17/call Gemini + Telnyx solution 5→**Savings:** $111,252/year at 100 clients 6→**Setup Time:** 2-3 hours initial, then fully automated 7→**Complexity:** Moderate (but worth it for economics) 8→ 9→--- 10→ 11→## TABLE OF CONTENTS 12→ 13→1. [System Architecture](#system-architecture) 14→2. [Cost Analysis](#cost-analysis) 15→3. [Technical Requirements](#technical-requirements) 16→4. [Component Setup](#component-setup) 17→5. [Integration Workflows](#integration-workflows) 18→6. [Implementation Code](#implementation-code) 19→7. [Testing & Validation](#testing--validation) 20→8. [Deployment Checklist](#deployment-checklist) 21→9. [Troubleshooting](#troubleshooting) 22→ 23→--- 24→ 25→## SYSTEM ARCHITECTURE 26→ 27→### High-Level Flow 28→ 29→``` 30→┌─────────────────────────────────────────────────────────────────┐ 31→│ CUSTOMER JOURNEY │ 32→└─────────────────────────────────────────────────────────────────┘ 33→ 34→1. Email Campaign (Gift + Audit) 35→ ↓ 36→2. Landing Page with Consent Form (GHL) 37→ ↓ 38→3. Form Submission → GHL Webhook 39→ ↓ 40→4. Genesis Orchestration Layer (n8n on Elestio) 41→ ↓ 42→5. Telnyx Voice API → Places Call 43→ ↓ 44→6. Gemini 2.5 Flash Native Audio → Conducts Conversation 45→ ↓ 46→7. Gemini Updates GHL (via Mastanley MCP during call) 47→ ↓ 48→8. Call Ends → Outcome Webhook → GHL 49→ ↓ 50→9. GHL Workflow → SMS Payment Link → Onboarding 51→``` 52→ 53→### Component Diagram 54→ 55→``` 56→┌──────────────┐ 57→│ GHL Form │ Consent checkbox, prospect data 58→└──────┬───────┘ 59→ │ Webhook 60→ ↓ 61→┌──────────────┐ 62→│ Orchestrator │ n8n (Elestio - Genesis Engine) 63→│ │ - Formats context for Gemini 64→│ │ - Triggers Telnyx call 65→│ │ - Logs to GHL 66→└──────┬───────┘ 67→ │ API Call 68→ ↓ 69→┌──────────────┐ 70→│ Telnyx │ Places outbound call 71→│ Voice API │ Routes audio to Gemini 72→└──────┬───────┘ 73→ │ SIP/WebRTC 74→ ↓ 75→┌──────────────┐ 76→│ Gemini │ Conducts conversation 77→│ 2.5 Flash │ Native Audio I/O 78→│ │ Mastanley MCP → Updates GHL 79→└──────┬───────┘ 80→ │ Webhook (call complete) 81→ ↓ 82→┌──────────────┐ 83→│ GHL Workflow │ Payment link, onboarding 84→└──────────────┘ 85→``` 86→ 87→--- 88→ 89→## COST ANALYSIS 90→ 91→### Per-Call Economics 92→ 93→| Component | Cost per Call | 94→|-----------|---------------| 95→| Telnyx outbound call (10 min avg) | $0.14 | 96→| Gemini 2.5 Flash tokens (~5K in/out) | $0.003 | 97→| Phone number (amortized) | $0.02 | 98→| Orchestration (n8n self-hosted) | $0.00 | 99→| **TOTAL PER CALL** | **$0.163** | 100→ 101→### Monthly Costs at Scale 102→ 103→| Clients | GHL Voice AI | Gemini + Telnyx (n8n) | Monthly Savings | 104→|---------|--------------|------------------------|-----------------| 105→| 10 | $970 | $14 | $956 | 106→| 50 | $4,850 | $186 | $4,664 | 107→| 100 | $9,700 | $400 | $9,300 | 108→| 500 | $48,500 | $2,116 | $46,384 | 109→ 110→**Assumptions:** 111→- 10 calls per client over their lifetime (initial + follow-ups) 112→- 10-minute average call duration 113→- $2/mo per Telnyx phone number 114→- n8n self-hosted on existing Elestio infrastructure ($0/mo) 115→ 116→### Break-Even Analysis 117→ 118→**Setup investment:** ~3 hours @ $100/hr = $300 119→**Break-even at:** 4 clients ($300 / $97 savings per client) 120→**After 4 clients:** Pure savings forever 121→ 122→--- 123→ 124→## TECHNICAL REQUIREMENTS 125→ 126→### Services Needed 127→ 128→1. **Telnyx Account** 129→ - Voice API access 130→ - At least one Australian phone number (+61) 131→ - SIP connection configured 132→ - Webhook endpoints set up 133→ 134→2. **Anthropic API** 135→ - Gemini 2.5 Flash access 136→ - Native Audio API enabled 137→ - API key with sufficient credits 138→ 139→3. **Genesis Orchestration (n8n on Elestio)** 140→ - Already deployed as part of Genesis infrastructure 141→ - Webhook nodes configured 142→ - HTTP request capabilities 143→ - Access to GHL, Telnyx, Gemini APIs 144→ - Persistent workflows across restarts 145→ 146→4. **GoHighLevel** 147→ - Webhook actions enabled 148→ - Custom fields configured 149→ - API access (for Mastanley MCP) 150→ 151→5. **Mastanley MCP** 152→ - Installed and configured 153→ - Connected to your GHL account 154→ - Tested for contact updates 155→ 156→### Prerequisites 157→ 158→- Telnyx API key 159→- Anthropic API key 160→- GHL API key (for Mastanley) 161→- Domain for webhook endpoints (if using CF Worker) 162→- Basic understanding of JSON formatting 163→ 164→--- 165→ 166→## COMPONENT SETUP 167→ 168→### 1. TELNYX SETUP (15 minutes) 169→ 170→#### Step 1.1: Create Account 171→``` 172→1. Go to telnyx.com 173→2. Sign up for account 174→3. Add payment method 175→4. Verify identity (required for voice services) 176→``` 177→ 178→#### Step 1.2: Buy Australian Phone Number 179→``` 180→1. Portal → Phone Numbers → Buy Numbers 181→2. Filter: Country = Australia, Type = Local 182→3. Search by area code: 02 (Sydney), 03 (Melbourne), 07 (Brisbane) 183→4. Purchase number ($2/mo) 184→5. Note the number for later: +61XXXXXXXXX 185→``` 186→ 187→#### Step 1.3: Create Voice Application 188→``` 189→1. Portal → Voice → Applications → Create 190→2. Application Name: "AgileAdapt AI Voice" 191→3. Webhook URL: [Will fill after orchestrator setup] 192→4. Webhook Timeout: 30 seconds 193→5. Save Application 194→6. Note Application ID 195→``` 196→ 197→#### Step 1.4: Assign Number to Application 198→``` 199→1. Phone Numbers → Select your number 200→2. Connection Settings → Voice Application 201→3. Select "AgileAdapt AI Voice" 202→4. Save 203→``` 204→ 205→#### Step 1.5: Get API Credentials 206→``` 207→1. Portal → API Keys → Create API Key 208→2. Name: "Gemini Voice Integration" 209→3. Permissions: Voice (all) 210→4. Copy API Key (v2 format) 211→5. Store securely 212→``` 213→ 214→**Telnyx Configuration Complete ✓** 215→ 216→--- 217→ 218→### 2. GEMINI SETUP (20 minutes) 219→ 220→#### Step 2.1: Enable Gemini API 221→``` 222→1. Go to console.anthropic.com 223→2. Navigate to API Keys 224→3. Create new key: "Telnyx Voice Agent" 225→4. Copy key and store securely 226→5. Check billing limits ($36 available confirmed) 227→``` 228→ 229→#### Step 2.2: Test Audio Capabilities 230→```bash 231→# Test Gemini 2.5 Flash Native Audio 232→curl https://api.anthropic.com/v1/messages \ 233→ -H "x-api-key: $ANTHROPIC_API_KEY" \ 234→ -H "anthropic-version: 2023-06-01" \ 235→ -H "content-type: application/json" \ 236→ -d '{ 237→ "model": "claude-sonnet-4-20250514", 238→ "max_tokens": 1024, 239→ "messages": [{ 240→ "role": "user", 241→ "content": "Test message for voice capability check" 242→ }] 243→ }' 244→``` 245→ 246→**Note:** As of Jan 2025, Claude Sonnet 4 doesn't have native audio input/output like Gemini. 247→ 248→**CRITICAL DECISION POINT:** 249→ 250→We need to check if you want: 251→- **Option A:** Use Gemini Live API (Google's Gemini 2.0 with native audio) 252→- **Option B:** Use Claude + speech-to-text/text-to-speech bridge (Deepgram or similar) 253→ 254→For Australian accents and true voice AI, **Gemini Live API is likely better**. 255→ 256→Let me revise this to use **Google Gemini 2.0 Flash** instead: 257→ 258→#### Step 2.2 (REVISED): Set Up Gemini Live API 259→```bash 260→# Install Google Cloud SDK 261→# Get API key from Google AI Studio 262→# https://aistudio.google.com/apikey 263→ 264→# Test Gemini 2.0 Flash 265→curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ 266→ -H 'Content-Type: application/json' \ 267→ -d '{ 268→ "contents": [{ 269→ "parts": [{ 270→ "text": "Test voice capability" 271→ }] 272→ }] 273→ }' 274→``` 275→ 276→#### Step 2.3: Configure System Prompt 277→ 278→Create file: `gemini_system_prompt.txt` 279→ 280→``` 281→You are Alex, an AI sales agent for AgileAdapt, calling Australian tradies who requested a social media audit. 282→ 283→CONTEXT PROVIDED: 284→- Prospect name: {name} 285→- Business name: {business} 286→- Phone: {phone} 287→- Last social post: {last_post_date} 288→- Months inactive: {months_inactive} 289→ 290→YOUR GOAL: 291→Close them on $497/month social media management service. 292→ 293→CONVERSATION FLOW: 294→ 295→1. OPENING (Warm) 296→"G'day {name}, it's Alex from AgileAdapt. I'm calling about the social media audit you requested for {business}. Got a couple minutes?" 297→ 298→[Wait for response] 299→ 300→2. VALUE DELIVERY (Show the problem) 301→"I had a look at your Facebook - last post was {last_post_date}, so about {months_inactive} months ago. That's actually costing you jobs because customers think you're closed or you've gone out of business." 302→ 303→"I made you 3 professional posts to fix that - they're in the email I sent. Did you get a chance to look at those?" 304→ 305→3. TRANSITION TO OFFER 306→"If you like those, I can keep this going for you. It's $497 a month, and I'll create 15 to 20 branded posts and they'll auto-publish to your Facebook. You never lose another customer because your social looks dead." 307→ 308→4. HANDLE OBJECTIONS 309→ 310→If "Too expensive": 311→"Yeah I get it - $497 seems like a lot. But one plumbing job is what, $500 to $800? If being active on social gets you just one extra job a month, this more than pays for itself. And most tradies get 2-3 extra jobs just from looking legit online." 312→ 313→If "Need to think about it": 314→"No worries mate, totally fair. How about this - I'll send you 3 more sample posts so you can see what it looks like, and I'll give you a bell on Friday at 10am. Does that work for you?" 315→ 316→If "I'm doing it myself": 317→"Good on ya! Are you keeping up with it consistently? Most tradies I talk to start strong then fall off after a month or two. If that happens, you know where to find me." 318→ 319→If "Not interested": 320→"All good mate, no worries. If you change your mind or things get too busy, just reply to that email. Cheers." 321→ 322→5. THE CLOSE 323→"Let me get you sorted then. $497 a month, you can cancel anytime. I'm texting you a payment link right now - just tap it and enter your card. Once that's done, I'll have your first 20 posts ready by Monday. Sound good?" 324→ 325→[Wait for confirmation] 326→ 327→6. DURING PAYMENT 328→"Check your phone - you should see a text from AgileAdapt with the Stripe link. Just tap it, enter your card details... I'll wait right here." 329→ 330→[Pause 15-20 seconds] 331→ 332→"Perfect, I can see that went through on my end. You'll get a welcome email in about 5 minutes with your login details. Your posts start going out Monday morning. Any questions?" 333→ 334→7. CLOSING 335→"Beauty, thanks {name}. If anything comes up, just reply to that welcome email. Have a good one!" 336→ 337→VOICE STYLE: 338→- Casual Australian accent 339→- Use "mate", "g'day", "no worries" 340→- Friendly but professional 341→- Don't sound like a robot 342→- Natural pauses 343→- Mirror their energy level 344→ 345→CRITICAL RULES: 346→- NEVER be pushy or aggressive 347→- If they say no twice, wish them well and end 348→- If they're rude, stay professional: "No worries mate, I'll let you go. Cheers." 349→- Log outcome immediately after call 350→ 351→OUTCOME TAGGING: 352→After call, you MUST update GHL contact with: 353→- Tag: "Call completed" 354→- Custom field "call_outcome": [Hot lead / Interested / Follow-up / Not interested / Wrong number / No answer] 355→- Custom field "call_notes": [Brief summary] 356→- Next action: [Send samples / Call Friday / Mark dead] 357→``` 358→ 359→--- 360→ 361→### 3. N8N GENESIS ORCHESTRATION SETUP (30 minutes) 362→ 363→**Why n8n:** Already deployed on Elestio as Genesis automation engine, powerful self-hosted, no monthly fees, full control. 364→ 365→#### Step 3.1: Access n8n Instance 366→``` 367→1. Go to your Elestio n8n dashboard 368→2. Login with Genesis credentials 369→3. Navigate to Workflows 370→4. Create new workflow: "Gemini Voice AI Pipeline" 371→``` 372→ 373→#### Step 3.2: Create Workflow 374→ 375→**Node 1: Webhook Trigger** 376→``` 377→1. Add node: Webhook 378→2. HTTP Method: POST 379→3. Path: /voice/ghl-consent 380→4. Response: Return Last Node 381→5. Copy webhook URL: https://your-n8n.elestio.app/webhook/voice/ghl-consent 382→6. Test: Click "Listen for Test Event" 383→``` 384→ 385→**Node 2: Set Variables (Parse GHL Data)** 386→``` 387→Add node: Set 388→Name: Parse GHL Webhook 389→ 390→Set fields: 391→- name: {{ $json.body.name }} 392→- phone: {{ $json.body.phone }} 393→- business: {{ $json.body.business_name }} 394→- email: {{ $json.body.email }} 395→- last_post: {{ $json.body.customField.last_post_date }} 396→- months_inactive: {{ $json.body.customField.months_inactive }} 397→- contact_id: {{ $json.body.contact_id }} 398→``` 399→ 400→**Node 3: Function - Format Gemini Context** 401→``` 402→Add node: Function 403→Name: Build Gemini Context 404→ 405→Code: 406→return [{ 407→ json: { 408→ gemini_context: { 409→ name: $input.first().json.name, 410→ business: $input.first().json.business, 411→ phone: $input.first().json.phone, 412→ last_post_date: $input.first().json.last_post, 413→ months_inactive: $input.first().json.months_inactive 414→ }, 415→ gemini_context_b64: Buffer.from(JSON.stringify({ 416→ name: $input.first().json.name, 417→ business: $input.first().json.business, 418→ phone: $input.first().json.phone, 419→ last_post_date: $input.first().json.last_post, 420→ months_inactive: $input.first().json.months_inactive 421→ })).toString('base64') 422→ } 423→}]; 424→``` 425→ 426→**Node 4: HTTP Request - Telnyx Call API** 427→``` 428→Add node: HTTP Request 429→Name: Place Telnyx Call 430→ 431→Method: POST 432→URL: https://api.telnyx.com/v2/calls 433→ 434→Authentication: Generic Credential Type 435→- Header Auth: Authorization 436→- Value: Bearer {{ $credentials.telnyx_api_key }} 437→ 438→Body Content Type: JSON 439→Body: 440→{ 441→ "connection_id": "{{ $credentials.telnyx_connection_id }}", 442→ "to": "{{ $node['Set'].json.phone }}", 443→ "from": "{{ $credentials.telnyx_phone_number }}", 444→ "webhook_url": "{{ $credentials.gemini_webhook_endpoint }}", 445→ "webhook_url_method": "POST", 446→ "client_state": "{{ $node['Function'].json.gemini_context_b64 }}" 447→} 448→``` 449→ 450→**Node 5: HTTP Request - Log Call Initiated to GHL** 451→``` 452→Add node: HTTP Request 453→Name: Update GHL - Call Initiated 454→ 455→Method: PATCH 456→URL: https://rest.gohighlevel.com/v1/contacts/{{ $node['Set'].json.contact_id }} 457→ 458→Authentication: Generic Credential Type 459→- Header Auth: Authorization 460→- Value: Bearer {{ $credentials.ghl_api_key }} 461→ 462→Body: 463→{ 464→ "tags": ["AI call initiated", "{{ new Date().toISOString() }}"], 465→ "customField": { 466→ "call_status": "In progress", 467→ "call_initiated_at": "{{ new Date().toISOString() }}" 468→ } 469→} 470→``` 471→ 472→**Node 6: Webhook - Call Outcome Receiver** 473→``` 474→Create NEW workflow: "Voice Call Outcome Handler" 475→ 476→Node 1: Webhook 477→- HTTP Method: POST 478→- Path: /voice/call-outcome 479→- Copy webhook URL (configure in Gemini CloudFlare Worker) 480→ 481→Node 2: Set Variables 482→- call_id: {{ $json.body.call_id }} 483→- call_outcome: {{ $json.body.summary.outcome }} 484→- call_notes: {{ $json.body.summary.notes }} 485→- call_duration: {{ $json.body.summary.duration }} 486→- contact_id: {{ $json.body.client_state.contact_id }} 487→``` 488→ 489→**Node 7: HTTP Request - Update GHL with Outcome** 490→``` 491→Add node: HTTP Request 492→Name: Update GHL - Call Complete 493→ 494→Method: PATCH 495→URL: https://rest.gohighlevel.com/v1/contacts/{{ $node['Set Variables'].json.contact_id }} 496→ 497→Body: 498→{ 499→ "tags": ["Call completed", "{{ $node['Set Variables'].json.call_outcome }}"], 500→ "customField": { 501→ "call_outcome": "{{ $node['Set Variables'].json.call_outcome }}", 502→ "call_notes": "{{ $node['Set Variables'].json.call_notes }}", 503→ "call_duration": "{{ $node['Set Variables'].json.call_duration }}", 504→ "call_completed_at": "{{ new Date().toISOString() }}" 505→ } 506→} 507→``` 508→ 509→**Node 8: Switch - Conditional Routing** 510→``` 511→Add node: Switch 512→Name: Route Based on Outcome 513→ 514→Mode: Rules 515→ 516→Rule 1: {{ $json.call_outcome }} = "Hot lead" 517→ → Route to: HTTP Request (Trigger GHL Workflow - Payment Link) 518→ 519→Rule 2: {{ $json.call_outcome }} = "Follow-up" 520→ → Route to: HTTP Request (Trigger GHL Workflow - Schedule Callback) 521→ 522→Rule 3: {{ $json.call_outcome }} = "Not interested" 523→ → Route to: HTTP Request (Trigger GHL Workflow - Nurture) 524→``` 525→ 526→**Node 9: HTTP Request - Trigger GHL Workflow (Payment Link)** 527→``` 528→Add node: HTTP Request 529→Name: Send Payment Link 530→ 531→Method: POST 532→URL: https://rest.gohighlevel.com/v1/workflows/{{ $credentials.ghl_payment_workflow_id }}/subscribe 533→ 534→Body: 535→{ 536→ "contact_id": "{{ $node['Set Variables'].json.contact_id }}" 537→} 538→``` 539→ 540→#### Step 3.3: Configure Credentials in n8n 541→``` 542→1. Click "Credentials" in top right 543→2. Add new credential: "Telnyx API" 544→ - telnyx_api_key: [Your Telnyx API Key] 545→ - telnyx_connection_id: [Your Connection ID] 546→ - telnyx_phone_number: +61XXXXXXXXX 547→ - gemini_webhook_endpoint: [Your CloudFlare Worker URL] 548→ 549→3. Add new credential: "GHL API" 550→ - ghl_api_key: [Your GHL API Key] 551→ - ghl_payment_workflow_id: [Workflow ID for payment] 552→ - ghl_callback_workflow_id: [Workflow ID for callbacks] 553→ - ghl_nurture_workflow_id: [Workflow ID for nurture] 554→``` 555→ 556→#### Step 3.4: Test & Activate 557→``` 558→1. Click "Listen for Test Event" on Webhook node 559→2. Submit test form from GHL 560→3. Watch execution flow in n8n 561→4. Verify call placement in Telnyx dashboard 562→5. Once working, click "Active" toggle 563→``` 564→ 565→**n8n workflows now active and monitoring ✓** 566→ 567→--- 568→ 569→### 4. GEMINI VOICE SERVER SETUP (45 minutes) 570→ 571→**This is the trickiest part** - Gemini needs a server to receive audio from Telnyx and respond. 572→ 573→**Options:** 574→1. **Cloudflare Worker** (recommended: free tier, auto-scaling) 575→2. **Heroku** (easy but costs $7/mo) 576→3. **Railway.app** (similar to Heroku) 577→4. **Self-hosted** (cheapest long-term but complex) 578→ 579→I'll show **Cloudflare Worker approach**: 580→ 581→#### Step 4.1: Create Cloudflare Account 582→``` 583→1. Go to cloudflare.com 584→2. Sign up for account (free tier is fine) 585→3. Add a domain or use workers.dev subdomain 586→``` 587→ 588→#### Step 4.2: Install Wrangler CLI 589→```bash 590→npm install -g wrangler 591→wrangler login 592→``` 593→ 594→#### Step 4.3: Create Worker Project 595→```bash 596→mkdir gemini-voice-agent 597→cd gemini-voice-agent 598→wrangler init 599→``` 600→ 601→#### Step 4.4: Worker Code (see next section) 602→ 603→--- 604→ 605→### 5. GHL WEBHOOK CONFIGURATION (10 minutes) 606→ 607→#### Step 5.1: Create Custom Form 608→``` 609→1. GHL → Sites → Forms 610→2. Create new form: "Request AI Explanation Call" 611→3. Fields: 612→ - Name (required) 613→ - Phone (required, AU format validation) 614→ - Email 615→ - Business Name (required) 616→4. Add checkbox: "I consent to receive a call from an AI agent to discuss my audit" 617→5. Hidden fields (populated from email link): 618→ - last_post_date 619→ - months_inactive 620→ - audit_url 621→``` 622→ 623→#### Step 5.2: Form Submission Webhook 624→``` 625→1. Form Settings → Integrations 626→2. Add webhook 627→3. URL: [Your Make.com webhook URL from earlier] 628→4. Method: POST 629→5. Test submission 630→``` 631→ 632→#### Step 5.3: Create GHL Workflows 633→ 634→**Workflow: Send Payment Link** 635→``` 636→Trigger: Tag added "Payment link requested" 637→Actions: 638→1. Send SMS with Stripe payment link 639→2. Wait 5 minutes 640→3. If payment received: 641→ → Tag "Paying customer" 642→ → Trigger onboarding workflow 643→4. If no payment: 644→ → Wait 1 hour 645→ → Send reminder SMS 646→``` 647→ 648→**Workflow: Schedule Callback** 649→``` 650→Trigger: Tag added "Schedule callback" 651→Actions: 652→1. Create task for follow-up call 653→2. Set reminder for specified date/time 654→3. Trigger Make.com scenario at scheduled time 655→``` 656→ 657→--- 658→ 659→## IMPLEMENTATION CODE 660→ 661→### Cloudflare Worker: Gemini Voice Bridge 662→ 663→File: `src/index.js` 664→ 665→```javascript 666→// Gemini Voice Agent - Cloudflare Worker 667→// Receives audio from Telnyx, processes with Gemini, returns audio 668→ 669→import { GoogleGenerativeAI } from "@google/generative-ai"; 670→ 671→export default { 672→ async fetch(request, env) { 673→ const url = new URL(request.url); 674→ 675→ // Handle Telnyx webhook 676→ if (url.pathname === "/voice/webhook") { 677→ return handleTelnyxWebhook(request, env); 678→ } 679→ 680→ // Handle call status updates 681→ if (url.pathname === "/voice/status") { 682→ return handleCallStatus(request, env); 683→ } 684→ 685→ return new Response("Gemini Voice Agent", { status: 200 }); 686→ }, 687→}; 688→ 689→async function handleTelnyxWebhook(request, env) { 690→ const payload = await request.json(); 691→ const event = payload.data.event_type; 692→ 693→ switch (event) { 694→ case "call.initiated": 695→ return handleCallInitiated(payload, env); 696→ 697→ case "call.answered": 698→ return handleCallAnswered(payload, env); 699→ 700→ case "call.speak.ended": 701→ return handleSpeakEnded(payload, env); 702→ 703→ case "call.hangup": 704→ return handleCallHangup(payload, env); 705→ 706→ default: 707→ console.log("Unhandled event:", event); 708→ return new Response("OK", { status: 200 }); 709→ } 710→} 711→ 712→async function handleCallAnswered(payload, env) { 713→ const callId = payload.data.payload.call_control_id; 714→ const clientState = JSON.parse( 715→ atob(payload.data.payload.client_state || "e30=") 716→ ); 717→ 718→ // Initialize Gemini session 719→ const genAI = new GoogleGenerativeAI(env.GEMINI_API_KEY); 720→ const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash-exp" }); 721→ 722→ // Start conversation with system prompt 723→ const systemPrompt = buildSystemPrompt(clientState); 724→ const opening = await generateOpening(model, systemPrompt, clientState); 725→ 726→ // Use Telnyx TTS to speak opening 727→ await telnyxSpeak(callId, opening, env.TELNYX_API_KEY); 728→ 729→ return new Response("OK", { status: 200 }); 730→} 731→ 732→async function handleSpeakEnded(payload, env) { 733→ const callId = payload.data.payload.call_control_id; 734→ 735→ // Start listening for prospect response 736→ await telnyxStartRecording(callId, env.TELNYX_API_KEY); 737→ 738→ return new Response("OK", { status: 200 }); 739→} 740→ 741→async function telnyxSpeak(callId, text, apiKey) { 742→ const response = await fetch(`https://api.telnyx.com/v2/calls/${callId}/actions/speak`, { 743→ method: "POST", 744→ headers: { 745→ "Authorization": `Bearer ${apiKey}`, 746→ "Content-Type": "application/json", 747→ }, 748→ body: JSON.stringify({ 749→ payload: text, 750→ voice: "female", // Use Telnyx Australian voice 751→ language: "en-AU", 752→ }), 753→ }); 754→ 755→ return response.json(); 756→} 757→ 758→async function telnyxStartRecording(callId, apiKey) { 759→ const response = await fetch(`https://api.telnyx.com/v2/calls/${callId}/actions/record_start`, { 760→ method: "POST", 761→ headers: { 762→ "Authorization": `Bearer ${apiKey}`, 763→ "Content-Type": "application/json", 764→ }, 765→ body: JSON.stringify({ 766→ format: "wav", 767→ channels: "single", 768→ }), 769→ }); 770→ 771→ return response.json(); 772→} 773→ 774→function buildSystemPrompt(context) { 775→ return `You are Alex, an AI sales agent for AgileAdapt... 776→ 777→[FULL SYSTEM PROMPT FROM EARLIER] 778→ 779→Current prospect context: 780→- Name: ${context.name} 781→- Business: ${context.business} 782→- Last post: ${context.last_post_date} 783→- Inactive: ${context.months_inactive} months 784→`; 785→} 786→ 787→async function generateOpening(model, systemPrompt, context) { 788→ const result = await model.generateContent({ 789→ contents: [{ 790→ parts: [{ 791→ text: `${systemPrompt}\n\nGenerate the opening line for this call. Just the opening, nothing else.` 792→ }] 793→ }] 794→ }); 795→ 796→ return result.response.text(); 797→} 798→ 799→async function handleCallHangup(payload, env) { 800→ const callData = payload.data.payload; 801→ 802→ // Extract call summary 803→ const summary = { 804→ duration: callData.duration, 805→ outcome: determineOutcome(callData), 806→ notes: "Call completed", 807→ }; 808→ 809→ // Send to Make.com webhook 810→ await fetch(env.MAKECOM_CALLBACK_URL, { 811→ method: "POST", 812→ headers: { "Content-Type": "application/json" }, 813→ body: JSON.stringify({ 814→ call_id: callData.call_control_id, 815→ summary: summary, 816→ client_state: callData.client_state, 817→ }), 818→ }); 819→ 820→ return new Response("OK", { status: 200 }); 821→} 822→ 823→function determineOutcome(callData) { 824→ // Logic to analyze call and determine outcome 825→ // This would use Gemini to analyze the conversation 826→ // For now, placeholder 827→ return "completed"; 828→} 829→``` 830→ 831→**Deploy:** 832→```bash 833→wrangler publish 834→``` 835→ 836→**Note the worker URL:** `https://gemini-voice-agent.YOUR-SUBDOMAIN.workers.dev` 837→ 838→--- 839→ 840→## TESTING & VALIDATION 841→ 842→### Test Sequence 843→ 844→#### Test 1: Webhook Connectivity 845→```bash 846→# Test GHL → Make.com webhook 847→curl -X POST [YOUR_MAKECOM_WEBHOOK] \ 848→ -H "Content-Type: application/json" \ 849→ -d '{ 850→ "name": "Test User", 851→ "phone": "+61412345678", 852→ "business_name": "Test Plumbing", 853→ "contact_id": "test123" 854→ }' 855→ 856→# Check Make.com logs for successful receipt 857→``` 858→ 859→#### Test 2: Telnyx Call Placement 860→```bash 861→# Test Telnyx API directly 862→curl -X POST https://api.telnyx.com/v2/calls \ 863→ -H "Authorization: Bearer YOUR_TELNYX_KEY" \ 864→ -H "Content-Type: application/json" \ 865→ -d '{ 866→ "connection_id": "YOUR_CONNECTION_ID", 867→ "to": "+61YOUR_MOBILE", 868→ "from": "+61YOUR_TELNYX_NUMBER" 869→ }' 870→ 871→# Answer the call on your mobile 872→``` 873→ 874→#### Test 3: Gemini Response 875→```bash 876→# Test Gemini API 877→curl https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY \ 878→ -H 'Content-Type: application/json' \ 879→ -d '{ 880→ "contents": [{ 881→ "parts": [{ 882→ "text": "You are calling John from Smith Plumbing. Generate your opening line." 883→ }] 884→ }] 885→ }' 886→``` 887→ 888→#### Test 4: End-to-End 889→``` 890→1. Fill out GHL consent form with YOUR mobile number 891→2. Check n8n execution logs for workflow trigger 892→3. Should receive call within 60 seconds 893→4. Answer and have conversation 894→5. Check GHL for updated tags/fields 895→``` 896→ 897→--- 898→ 899→## DEPLOYMENT CHECKLIST 900→ 901→### Pre-Launch 902→ 903→- [ ] Telnyx account verified and funded 904→- [ ] Australian phone number purchased 905→- [ ] Gemini API key created and tested 906→- [ ] n8n workflows built and activated on Elestio 907→- [ ] Cloudflare Worker deployed 908→- [ ] GHL form created with consent checkbox 909→- [ ] GHL webhooks configured 910→- [ ] Stripe payment links created 911→- [ ] Test call successfully completed 912→- [ ] Call outcome updates GHL correctly 913→- [ ] Payment workflow triggers properly 914→ 915→### Legal Compliance 916→ 917→- [ ] Consent language reviewed by lawyer 918→- [ ] "Prior express written consent" included 919→- [ ] Disclosure that call may be AI-generated 920→- [ ] Opt-out instructions provided 921→- [ ] Do Not Call list checked (if applicable) 922→- [ ] Recording consent (if required in region) 923→- [ ] Privacy policy updated 924→ 925→### Monitoring Setup 926→ 927→- [ ] Telnyx webhook logs monitored 928→- [ ] n8n workflow execution logs monitored 929→- [ ] n8n error notifications configured 930→- [ ] Gemini API usage tracking enabled 931→- [ ] GHL dashboard for call outcomes 932→- [ ] Daily summary email of call activity 933→ 934→--- 935→ 936→## TROUBLESHOOTING 937→ 938→### Common Issues 939→ 940→#### Issue: Webhook not firing 941→**Symptoms:** Form submitted but no call placed 942→**Debug:** 943→```bash 944→# Check n8n workflow execution history 945→# Verify webhook URL is correct in GHL 946→# Test webhook manually with curl 947→``` 948→**Solution:** Re-copy webhook URL from n8n, ensure HTTPS 949→ 950→#### Issue: Call connects but silent 951→**Symptoms:** Call answered but no audio 952→**Debug:** 953→```bash 954→# Check Telnyx webhook logs 955→# Verify Gemini endpoint is responding 956→# Check audio codec compatibility 957→``` 958→**Solution:** Ensure Cloudflare Worker is returning proper audio format 959→ 960→#### Issue: Call drops after 30 seconds 961→**Symptoms:** Conversation starts then disconnects 962→**Debug:** 963→```bash 964→# Check Telnyx webhook timeout settings 965→# Verify Gemini response time < 10 seconds 966→# Check network connectivity 967→``` 968→**Solution:** Increase webhook timeout to 60 seconds 969→ 970→#### Issue: Wrong Australian accent 971→**Symptoms:** Voice sounds American/British 972→**Debug:** 973→```bash 974→# Check Telnyx voice settings 975→# Verify language parameter is "en-AU" 976→``` 977→**Solution:** Explicitly set `language: "en-AU"` in Telnyx speak command 978→ 979→#### Issue: GHL not updating after call 980→**Symptoms:** Call completes but no tags/fields updated 981→**Debug:** 982→```bash 983→# Check n8n "Update GHL - Call Complete" node logs 984→# Verify GHL API key has write permissions 985→# Check contact_id is correct 986→``` 987→**Solution:** Update GHL API permissions, verify contact ID mapping in n8n workflow 988→ 989→--- 990→ 991→## COST OPTIMIZATION 992→ 993→### Strategies to Reduce Costs 994→ 995→1. **Reuse Phone Numbers** 996→ - Use 1 number for first 100 clients 997→ - Only add numbers if volume > 50 calls/day per number 998→ 999→2. **Batch Follow-ups** 1000→ - Schedule callbacks during off-peak hours 1001→ - Group calls to same area code 1002→ 1003→3. **Reduce Call Duration** 1004→ - Tighten system prompt 1005→ - Faster qualification questions 1006→ - Auto-end after 2 "not interested" responses 1007→ 1008→4. **Use Free Tiers** 1009→ - Cloudflare Workers: 100K requests/day free 1010→ - n8n: Self-hosted on existing Elestio infrastructure ($0/mo) 1011→ - Scale infrastructure only when needed 1012→ 1013→5. **Optimize Gemini Usage** 1014→ - Cache system prompts 1015→ - Use smaller context windows 1016→ - Stream responses instead of full completion 1017→ 1018→--- 1019→ 1020→## SCALING CONSIDERATIONS 1021→ 1022→### At 100 Clients/Month 1023→ 1024→**Call Volume:** ~1,000 calls/month (10 per client) 1025→**n8n:** Self-hosted on Elestio ($0/mo) 1026→**Telnyx:** 50 phone numbers ($100/mo) 1027→**Gemini:** ~$30/mo in API costs 1028→**Total:** ~$130/mo 1029→ 1030→**Compare to GHL:** $9,700/mo 1031→**Savings:** $9,570/mo 1032→ 1033→### At 500 Clients/Month 1034→ 1035→**Call Volume:** ~5,000 calls/month 1036→**n8n:** Self-hosted on Elestio ($0/mo) 1037→**Telnyx:** 250 phone numbers ($500/mo) 1038→**Gemini:** ~$150/mo in API costs 1039→**Total:** ~$650/mo 1040→ 1041→**Compare to GHL:** $48,500/mo 1042→**Savings:** $47,850/mo 1043→ 1044→--- 1045→ 1046→## NEXT STEPS 1047→ 1048→1. **Set up Telnyx account** (today) 1049→2. **Create n8n workflows on Elestio** (today) 1050→3. **Deploy Cloudflare Worker** (tomorrow) 1051→4. **Test end-to-end** (tomorrow) 1052→5. **Launch with 10 test leads** (day 3) 1053→6. **Refine based on results** (day 4-7) 1054→7. **Scale to 250 leads** (week 2) 1055→ 1056→--- 1057→ 1058→## SUPPORT RESOURCES 1059→ 1060→- Telnyx Docs: https://developers.telnyx.com 1061→- Gemini API: https://ai.google.dev/docs 1062→- n8n Documentation: https://docs.n8n.io 1063→- Elestio n8n Guide: https://elest.io/open-source/n8n 1064→- GHL API Docs: https://highlevel.stoplight.io 1065→ 1066→--- 1067→ 1068→**Document Version:** 1.1 (Updated for n8n) 1069→**Last Updated:** January 8, 2026 1070→**Author:** Genesis Protocol 1071→**Status:** Ready for Implementation with n8n Genesis Engine 1072→ Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.