AutomationFlows β€Ί AI & RAG β€Ί Qualify and Manage Voice Sales Calls with Claude, Gpt-4o, Gemini, and…

Qualify and Manage Voice Sales Calls with Claude, Gpt-4o, Gemini, and…

Original n8n title: Qualify and Manage Voice Sales Calls with Claude, Gpt-4o, Gemini, and Gohighlevel

ByKumar SmartFlow Craft @smartflowautomateβœ“ on n8n.io

⏺ πŸš€ How it works

Webhook triggerβ˜…β˜…β˜…β˜…β˜… complexityAI-powered94 nodesAgentAnthropic ChatOutput Parser StructuredOpenAI ChatHigh LevelGoogle Gemini ChatSupabaseGoogle Sheets
AI & RAG Trigger: Webhook Nodes: 94 Complexity: β˜…β˜…β˜…β˜…β˜… AI nodes: yes Added:

This workflow corresponds to n8n.io template #14169 β€” we link there as the canonical source.

This workflow follows the Agent β†’ Google Sheets recipe pattern β€” see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide β†’

Download .json
{
  "nodes": [
    {
      "id": "2c6149e5-01bc-4a87-903b-ce16f36f3ff4",
      "name": "Setup Guide",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        30688,
        6576
      ],
      "parameters": {
        "color": 7,
        "width": 636,
        "height": 928,
        "content": "## \ud83d\ude80 Voice AI + GHL Sales Agent \u2014 Setup Guide\n\n**STEP 1 \u2014 Credentials**\n- Add `HighLevel OAuth2` credential (OAuth2 flow via GHL marketplace app)\n- Add `Anthropic` API key credential\n- Add `OpenAI` API key credential\n- Add `Google Gemini (PaLM) API` credential\n- Add `Supabase` credential (URL + Service Role Key)\n- Add `Google Sheets OAuth2` credential\n\n**STEP 2 \u2014 Supabase**\n- Run the CREATE TABLE SQL shown in the Supabase Schema note\n- Replace `YOUR_SUPABASE_CRED_ID` in all Supabase nodes\n\n**STEP 3 \u2014 GoHighLevel**\n- Replace `YOUR_HL_CRED_ID` with your GHL credential ID\n- Replace `YOUR_PIPELINE_ID` with your GHL pipeline ID\n- Replace `YOUR_HOT_STAGE_ID` with the Hot Lead stage ID\n- Replace `YOUR_NURTURING_STAGE_ID` with the Nurturing stage ID\n- Replace `YOUR_NURTURE_WORKFLOW_ID` with your GHL automation workflow ID\n\n**STEP 4 \u2014 Voice Provider (Vapi)**\n- Set env var `VAPI_API_KEY` in n8n Settings \u2192 Environment Variables\n- Replace `YOUR_VAPI_PHONE_NUMBER_ID` with your Vapi phone number ID\n- Replace `YOUR_VAPI_ASSISTANT_ID` with your Vapi assistant ID\n- Inbound webhook URL: `https://YOUR_N8N_URL/webhook/voice-sales-inbound`\n- Configure Vapi End-of-Call webhook to POST to the inbound URL\n- For Retell: configure `call_ended` webhook to POST to same URL\n\n**STEP 5 \u2014 Google Sheets**\n- Create a spreadsheet with sheet named `Voice Call Log`\n- Add all column headers listed in the Sheets Columns note\n- Replace `YOUR_SPREADSHEET_ID` with the spreadsheet ID from the URL\n\n**STEP 6 \u2014 GHL API Key (for Trigger Nurture Workflow)**\n- Set env var `GHL_API_KEY` in n8n Settings \u2192 Environment Variables\n- This is your GHL Location API key (Settings \u2192 API Keys)\n\n**STEP 7 \u2014 Activate**\n- Activate the workflow\n- Test with a short test call to verify BANT scoring\n- Monitor the Voice Call Log sheet for entries"
      },
      "typeVersion": 1
    },
    {
      "id": "b038c1cd-3d50-4088-b140-55f41d5f7240",
      "name": "Supabase Schema",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31376,
        6512
      ],
      "parameters": {
        "color": 5,
        "width": 520,
        "height": 988,
        "content": "## \ud83d\uddc4\ufe0f Supabase Table Schema\n\n```sql\nCREATE TABLE voice_call_logs (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  call_id TEXT NOT NULL,\n  phone_number TEXT,\n  direction TEXT DEFAULT 'inbound',\n  provider TEXT,\n  duration_sec INTEGER,\n  transcript TEXT,\n  recording_url TEXT,\n  bant_score INTEGER,\n  qualified BOOLEAN DEFAULT FALSE,\n  budget_confirmed BOOLEAN,\n  authority_confirmed BOOLEAN,\n  need_identified BOOLEAN,\n  timeline_defined BOOLEAN,\n  bant_summary TEXT,\n  key_objections JSONB,\n  appointment_requested BOOLEAN,\n  preferred_time TEXT,\n  ghl_contact_id TEXT,\n  ghl_opp_id TEXT,\n  crm_note TEXT,\n  objection_response TEXT,\n  outbound_call_id TEXT,\n  created_at TIMESTAMPTZ DEFAULT NOW()\n);\n\nCREATE INDEX idx_vcl_phone\n  ON voice_call_logs(phone_number);\nCREATE INDEX idx_vcl_qualified\n  ON voice_call_logs(qualified);\nCREATE INDEX idx_vcl_created\n  ON voice_call_logs(created_at DESC);\n```"
      },
      "typeVersion": 1
    },
    {
      "id": "8e3f1b65-ed3a-48dc-8dd8-0d19a7be1da0",
      "name": "Google Sheets Columns",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31920,
        6512
      ],
      "parameters": {
        "color": 5,
        "width": 380,
        "height": 980,
        "content": "## \ud83d\udcca Google Sheets Columns\n\nSheet name: **Voice Call Log**\n\nCreate these columns (Row 1 headers):\n1. Call ID\n2. Date\n3. Phone Number\n4. Direction\n5. Provider\n6. Duration (sec)\n7. BANT Score\n8. Qualified\n9. Appointment Requested\n10. GHL Contact ID\n11. GHL Opp ID\n12. CRM Note\n13. Objection Response"
      },
      "typeVersion": 1
    },
    {
      "id": "5b03b0d6-20a4-495d-ab30-89f3343f90cd",
      "name": "HighLevel IDs",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32336,
        6496
      ],
      "parameters": {
        "color": 6,
        "width": 480,
        "height": 996,
        "content": "## \ud83d\udd11 HighLevel Placeholder IDs\n\n| Placeholder | Where to find |\n|---|---|\n| `YOUR_HL_CRED_ID` | n8n Credentials page |\n| `YOUR_PIPELINE_ID` | GHL \u2192 Opportunities \u2192 Pipeline settings |\n| `YOUR_HOT_STAGE_ID` | GHL \u2192 Pipeline \u2192 Stage ID from URL |\n| `YOUR_NURTURING_STAGE_ID` | GHL \u2192 Pipeline \u2192 Stage ID from URL |\n| `YOUR_NURTURE_WORKFLOW_ID` | GHL \u2192 Automation \u2192 Workflow ID |\n\n**Finding Stage IDs:**\nGo to GHL Opportunities \u2192 Edit Pipeline\n\u2192 click a stage \u2192 check URL for the UUID\n\n**Finding Pipeline ID:**\nGHL Settings \u2192 Pipelines\n\u2192 click pipeline \u2192 UUID in URL\n\n**Vapi IDs:**\n| `YOUR_VAPI_PHONE_NUMBER_ID` | Vapi Dashboard \u2192 Phone Numbers |\n| `YOUR_VAPI_ASSISTANT_ID` | Vapi Dashboard \u2192 Assistants |"
      },
      "typeVersion": 1
    },
    {
      "id": "8766ca49-3c85-4668-abb5-399e30f93562",
      "name": "INBOUND FLOW",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        30976,
        7696
      ],
      "parameters": {
        "color": 4,
        "width": 600,
        "height": 50,
        "content": "## \ud83d\udcde INBOUND FLOW \u2014 Vapi / Retell Webhook \u2192 BANT \u2192 Booking \u2192 HighLevel CRM"
      },
      "typeVersion": 1
    },
    {
      "id": "d4221ed4-192d-42aa-a900-6ce94e1bdc0f",
      "name": "Inbound Call Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        30960,
        7920
      ],
      "parameters": {
        "path": "voice-sales-inbound",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2
    },
    {
      "id": "ff5adfec-1e48-4860-93e4-ea46f0147454",
      "name": "Normalize Call Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        31200,
        7920
      ],
      "parameters": {
        "jsCode": "// Normalize Vapi and Retell webhook payloads into a unified structure\nconst items = $input.all();\nconst body = items[0].json.body || items[0].json;\n\nlet call_id, phone_number, transcript, recording_url, duration_sec, provider;\n\n// Detect Vapi format\nif (body.message && (body.message.type === 'end-of-call-report' || body.message.type === 'end-of-call')) {\n  provider = 'vapi';\n  const msg = body.message;\n  call_id = (msg.call && msg.call.id) || msg.callId || 'unknown';\n  phone_number = (msg.call && msg.call.customer && msg.call.customer.number) || msg.customerNumber || '';\n  transcript = msg.transcript || msg.artifactTranscript || '';\n  recording_url = msg.recordingUrl || (msg.artifact && msg.artifact.recordingUrl) || '';\n  duration_sec = (msg.call && msg.call.duration) || msg.duration || 0;\n\n// Detect Retell format\n} else if (body.event === 'call_ended' || body.event === 'call_analyzed') {\n  provider = 'retell';\n  const data = body.data || body;\n  call_id = data.call_id || data.callId || 'unknown';\n  phone_number = data.from_number || data.fromNumber || data.customer_number || '';\n  // Retell transcript may be an array of turn objects\n  if (Array.isArray(data.transcript)) {\n    transcript = data.transcript.map(t => `${t.role || t.speaker}: ${t.content || t.text}`).join('\\n');\n  } else {\n    transcript = data.transcript || '';\n  }\n  recording_url = data.recording_url || data.recordingUrl || '';\n  duration_sec = data.duration_sec || (data.duration_ms ? Math.round(data.duration_ms / 1000) : 0);\n\n// Fallback / generic format\n} else {\n  provider = 'unknown';\n  call_id = body.call_id || body.callId || 'unknown-' + Date.now();\n  phone_number = body.phone_number || body.from || body.customer_number || '';\n  transcript = body.transcript || '';\n  recording_url = body.recording_url || '';\n  duration_sec = body.duration_sec || body.duration || 0;\n}\n\n// Normalize phone to E.164\nif (phone_number && !phone_number.startsWith('+')) {\n  const digits = phone_number.replace(/\\D/g, '');\n  phone_number = digits.length === 10 ? '+1' + digits : '+' + digits;\n}\n\nreturn [{\n  json: {\n    call_id,\n    phone_number,\n    transcript,\n    recording_url,\n    duration_sec: parseInt(duration_sec) || 0,\n    provider,\n    raw_body: body\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "e12f23c5-2490-4993-b4dc-c828de36a6f5",
      "name": "Valid Call?",
      "type": "n8n-nodes-base.if",
      "position": [
        31440,
        7920
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-duration",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.duration_sec }}",
              "rightValue": 30
            },
            {
              "id": "cond-transcript",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.transcript.length }}",
              "rightValue": 50
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "6bc7ecd9-aa1e-461e-aa21-b1d297534e4c",
      "name": "BANT Qualifier",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        31680,
        7904
      ],
      "parameters": {
        "text": "=Phone Number: {{ $json.phone_number }}\nCall Duration: {{ $json.duration_sec }} seconds\nProvider: {{ $json.provider }}\n\nCALL TRANSCRIPT:\n{{ $json.transcript }}\n\nAnalyze this sales call transcript using the BANT framework and return your structured assessment.",
        "options": {
          "systemMessage": "You are an expert B2B sales qualification specialist with 15+ years of experience analyzing sales calls using the BANT framework (Budget, Authority, Need, Timeline).\n\nYour task is to analyze the provided call transcript and score the lead's qualification level.\n\nSCORING CRITERIA:\n- Budget (0-3 pts): 0=not discussed, 1=vague mention of budget, 2=budget range confirmed, 3=specific budget confirmed and allocated\n- Authority (0-3 pts): 0=unknown/gatekeeper, 1=influencer only, 2=shared decision-maker, 3=sole decision-maker\n- Need (0-2 pts): 0=no need identified, 1=pain point mentioned, 2=urgent clear business need confirmed\n- Timeline (0-2 pts): 0=no timeline mentioned, 1=vague future intent, 2=specific timeline or urgency expressed\n\nTotal score range: 0-10. Score >= 6 = qualified lead.\n\nKey objection extraction: pull exact phrases or paraphrased objections verbatim from the transcript.\nBANT summary: write a concise 2-3 sentence executive summary of the lead's qualification status and business situation.\n\nAlways respond with valid JSON matching the output schema exactly. Do not wrap in markdown code blocks."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.8
    },
    {
      "id": "50365d33-a693-44b4-89a7-0a7578012245",
      "name": "Claude Haiku (BANT)",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        31584,
        8128
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-3-haiku-20240307"
        },
        "options": {
          "temperature": 0.1
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "c768b57e-963d-45e7-8746-403e4e7f1603",
      "name": "BANT Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        31792,
        8128
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"score\": {\"type\": \"number\"}, \"budget_confirmed\": {\"type\": \"boolean\"}, \"authority_confirmed\": {\"type\": \"boolean\"}, \"need_identified\": {\"type\": \"boolean\"}, \"timeline_defined\": {\"type\": \"boolean\"}, \"summary\": {\"type\": \"string\"}, \"key_objections\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"score\", \"budget_confirmed\", \"authority_confirmed\", \"need_identified\", \"timeline_defined\", \"summary\", \"key_objections\"]}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "15f47f1f-bf86-40d0-8469-cebe0c38619e",
      "name": "Extract BANT",
      "type": "n8n-nodes-base.code",
      "position": [
        31984,
        7920
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst item = items[0];\n\n// The AI Agent output is in item.json.output (string) or already parsed object\nlet bant;\ntry {\n  const raw = item.json.output || item.json;\n  if (typeof raw === 'string') {\n    const cleaned = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n    bant = JSON.parse(cleaned);\n  } else if (typeof raw === 'object' && raw.score !== undefined) {\n    bant = raw;\n  } else if (raw.output && typeof raw.output === 'object') {\n    bant = raw.output;\n  } else {\n    bant = { score: 0, budget_confirmed: false, authority_confirmed: false, need_identified: false, timeline_defined: false, summary: 'Parse error', key_objections: [] };\n  }\n} catch(e) {\n  bant = { score: 0, budget_confirmed: false, authority_confirmed: false, need_identified: false, timeline_defined: false, summary: 'Parse error: ' + e.message, key_objections: [] };\n}\n\nreturn [{\n  json: {\n    ...item.json,\n    bant_score: bant.score || 0,\n    budget_confirmed: bant.budget_confirmed || false,\n    authority_confirmed: bant.authority_confirmed || false,\n    need_identified: bant.need_identified || false,\n    timeline_defined: bant.timeline_defined || false,\n    bant_summary: bant.summary || '',\n    key_objections: bant.key_objections || [],\n    qualified: (bant.score || 0) >= 6\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "8d35cf31-1c05-4092-93b5-aab3eb719bbc",
      "name": "Qualified?",
      "type": "n8n-nodes-base.if",
      "position": [
        32160,
        7920
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-qualified",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.bant_score }}",
              "rightValue": 6
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "1c9d4c01-47ee-4169-aee0-fde73118f5f5",
      "name": "Booking Intent Detector",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        32400,
        7920
      ],
      "parameters": {
        "text": "=Phone: {{ $json.phone_number }}\nBANT Score: {{ $json.bant_score }}/10\nBANT Summary: {{ $json.bant_summary }}\nKey Objections: {{ JSON.stringify($json.key_objections) }}\n\nCALL TRANSCRIPT:\n{{ $json.transcript }}\n\nAnalyze the booking intent and all objections from this qualified sales call.",
        "options": {
          "systemMessage": "You are an expert sales conversation analyst specializing in appointment setting and objection identification.\n\nYour task is to:\n1. Detect whether the prospect expressed intent to book a meeting, demo, or follow-up call\n2. Extract any preferred time or date mentioned by the prospect\n3. Rate your confidence in the booking intent on a scale of 0.0 to 1.0\n4. List ALL sales objections raised during the call (pricing, timing, competition, authority, need doubt, integration concerns, etc.)\n\nExplicit booking signals: 'book a call', 'schedule a demo', 'let's meet', 'send me a calendar link', 'set up a time'\nImplicit booking signals: 'I'd like to learn more', 'can you send details', 'when are you available', 'I'll think about it and we can talk'\n\nFor objections: capture the actual language or substance from the transcript, not generic categories.\n\nAlways respond with valid JSON matching the output schema exactly. Do not wrap in markdown code blocks."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.8
    },
    {
      "id": "34a49c29-44e5-45a2-b58a-8065b7cfde35",
      "name": "GPT-4o (Booking)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        32304,
        8128
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o"
        },
        "options": {
          "maxTokens": 800,
          "temperature": 0.1
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f56f0b20-5cef-4c2b-a0f0-57336c99430d",
      "name": "Booking Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        32512,
        8128
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"appointment_requested\": {\"type\": \"boolean\"}, \"preferred_time\": {\"type\": \"string\"}, \"confidence\": {\"type\": \"number\"}, \"objections\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"appointment_requested\", \"preferred_time\", \"confidence\", \"objections\"]}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "df626c1e-4cb1-45b4-9249-c60e92936493",
      "name": "Extract Booking",
      "type": "n8n-nodes-base.code",
      "position": [
        32704,
        7920
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst item = items[0];\n\nlet booking;\ntry {\n  const raw = item.json.output || item.json;\n  if (typeof raw === 'string') {\n    const cleaned = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n    booking = JSON.parse(cleaned);\n  } else if (typeof raw === 'object' && raw.appointment_requested !== undefined) {\n    booking = raw;\n  } else if (raw.output && typeof raw.output === 'object') {\n    booking = raw.output;\n  } else {\n    booking = { appointment_requested: false, preferred_time: '', confidence: 0, objections: [] };\n  }\n} catch(e) {\n  booking = { appointment_requested: false, preferred_time: '', confidence: 0, objections: [] };\n}\n\nreturn [{\n  json: {\n    ...item.json,\n    appointment_requested: booking.appointment_requested || false,\n    preferred_time: booking.preferred_time || '',\n    booking_confidence: booking.confidence || 0,\n    detected_objections: booking.objections || item.json.key_objections || []\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "2e5a0f2a-1316-4a26-bf83-0dae5a81d753",
      "name": "Search GHL Contact",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        32880,
        7920
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "query": "={{ $json.phone_number }}"
        },
        "options": {},
        "operation": "getAll",
        "requestOptions": {}
      },
      "typeVersion": 2
    },
    {
      "id": "7e34c94c-036f-4b36-85ff-d4813208c581",
      "name": "Contact Exists?",
      "type": "n8n-nodes-base.if",
      "position": [
        33120,
        7920
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-contact-exists",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "225e773a-d172-4b4c-8098-cc161d52c157",
      "name": "Update Contact",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        33360,
        7776
      ],
      "parameters": {
        "contactId": "={{ $json.id }}",
        "operation": "update",
        "updateFields": {
          "tags": "voice-lead,qualified"
        },
        "requestOptions": {}
      },
      "typeVersion": 2
    },
    {
      "id": "8baf9ac3-da6f-4f45-ab43-42894b2cdf21",
      "name": "Create Contact",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        33360,
        8064
      ],
      "parameters": {
        "phone": "={{ $('Extract Booking').item.json.phone_number }}",
        "requestOptions": {},
        "additionalFields": {
          "tags": "voice-lead,qualified",
          "source": "Voice AI",
          "firstName": "Voice Lead"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "e1830164-2269-4350-bac8-5caeb6fd53fa",
      "name": "Merge Contact Paths",
      "type": "n8n-nodes-base.merge",
      "position": [
        33600,
        7920
      ],
      "parameters": {
        "mode": "chooseBranch"
      },
      "typeVersion": 3
    },
    {
      "id": "2f73c33f-4668-4998-b644-2df3d023e98f",
      "name": "Create Hot Opportunity",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        33840,
        7920
      ],
      "parameters": {
        "name": "={{ 'Voice Lead \u2014 ' + $('Extract Booking').item.json.phone_number }}",
        "resource": "opportunity",
        "contactId": "={{ $json.id }}",
        "pipelineId": "YOUR_PIPELINE_ID",
        "requestOptions": {},
        "additionalFields": {
          "stageId": "YOUR_HOT_STAGE_ID",
          "monetaryValue": 0
        }
      },
      "typeVersion": 2
    },
    {
      "id": "0dfc8efc-c2d6-4c90-a35a-744ece82c633",
      "name": "Objection Handler",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        34080,
        7920
      ],
      "parameters": {
        "text": "=Lead Phone: {{ $('Extract Booking').item.json.phone_number }}\nBANT Score: {{ $('Extract BANT').item.json.bant_score }}/10\nAppointment Requested: {{ $('Extract Booking').item.json.appointment_requested }}\nPreferred Time: {{ $('Extract Booking').item.json.preferred_time }}\n\nObjections raised by the prospect:\n{{ JSON.stringify($('Extract Booking').item.json.detected_objections, null, 2) }}\n\nBANT Summary:\n{{ $('Extract BANT').item.json.bant_summary }}\n\nGenerate a professional objection handling response script for this qualified lead.",
        "options": {
          "systemMessage": "You are a world-class B2B sales coach specializing in objection handling. You have deep expertise in consultative selling, value-based selling, and advanced persuasion techniques.\n\nYour task is to:\n1. Analyze all objections raised by the prospect in priority order\n2. Select the single most effective objection handling technique:\n   - feel-felt-found: for emotional/uncertain objections ('I feel it's too expensive')\n   - acknowledge-clarify-respond: for factual/pricing objections with specific numbers\n   - reframe: for competitive/comparison objections\n   - isolate-and-solve: when there is one single blocking objection\n   - social-proof: when prospect is skeptical about results\n3. Write a specific, personalized response script for the sales rep to use on the follow-up call\n4. Rate your confidence (0.0-1.0) that this response will effectively address the objections\n\nThe response script requirements:\n- 3-5 sentences maximum\n- Natural, conversational tone (not corporate/stiff)\n- Reference the specific objection language or context from the call\n- End with a clear, low-pressure call-to-action\n- Should feel like it was written by a human sales coach, not a template\n\nAlways respond with valid JSON matching the output schema exactly. Do not wrap in markdown code blocks."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.8
    },
    {
      "id": "c35439d6-5c34-4e25-b650-25e2c89871b8",
      "name": "Claude Sonnet (Objection)",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        33984,
        8128
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-3-5-sonnet-20241022"
        },
        "options": {
          "temperature": 0.3
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "915179ba-1e73-4152-956c-83270f947bd9",
      "name": "Objection Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        34304,
        8128
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"response_script\": {\"type\": \"string\"}, \"technique\": {\"type\": \"string\"}, \"confidence\": {\"type\": \"number\"}}, \"required\": [\"response_script\", \"technique\", \"confidence\"]}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "e256abc8-7cc7-4c7b-bb30-0218f66cad54",
      "name": "Extract Objection Response",
      "type": "n8n-nodes-base.code",
      "position": [
        34368,
        7920
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst item = items[0];\n\nlet resp;\ntry {\n  const raw = item.json.output || item.json;\n  if (typeof raw === 'string') {\n    const cleaned = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n    resp = JSON.parse(cleaned);\n  } else if (typeof raw === 'object' && raw.response_script !== undefined) {\n    resp = raw;\n  } else if (raw.output && typeof raw.output === 'object') {\n    resp = raw.output;\n  } else {\n    resp = { response_script: 'Follow up with value proposition tailored to their specific pain points.', technique: 'generic', confidence: 0.5 };\n  }\n} catch(e) {\n  resp = { response_script: 'Follow up with value proposition tailored to their specific pain points.', technique: 'generic', confidence: 0.5 };\n}\n\n// Carry forward all previous context\nconst bant = $('Extract BANT').item.json;\nconst booking = $('Extract Booking').item.json;\nconst mergedContact = $('Merge Contact Paths').item.json;\nconst opp = $('Create Hot Opportunity').item.json;\n\nreturn [{\n  json: {\n    call_id: bant.call_id,\n    phone_number: bant.phone_number,\n    transcript: bant.transcript,\n    recording_url: bant.recording_url,\n    duration_sec: bant.duration_sec,\n    provider: bant.provider,\n    bant_score: bant.bant_score,\n    budget_confirmed: bant.budget_confirmed,\n    authority_confirmed: bant.authority_confirmed,\n    need_identified: bant.need_identified,\n    timeline_defined: bant.timeline_defined,\n    bant_summary: bant.bant_summary,\n    key_objections: bant.key_objections,\n    qualified: true,\n    appointment_requested: booking.appointment_requested,\n    preferred_time: booking.preferred_time,\n    booking_confidence: booking.booking_confidence,\n    detected_objections: booking.detected_objections,\n    ghl_contact_id: mergedContact.id || '',\n    ghl_opp_id: opp.id || '',\n    objection_response_script: resp.response_script,\n    objection_technique: resp.technique,\n    objection_confidence: resp.confidence\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "379041e0-2fcf-470b-8702-d510b525e0d1",
      "name": "CRM Note Writer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        34560,
        7920
      ],
      "parameters": {
        "text": "=Contact Phone: {{ $json.phone_number }}\nCall Date: {{ $now.toISO() }}\nCall Duration: {{ $json.duration_sec }} seconds\nProvider: {{ $json.provider }}\nBANT Score: {{ $json.bant_score }}/10\nBudget Confirmed: {{ $json.budget_confirmed }}\nAuthority Confirmed: {{ $json.authority_confirmed }}\nNeed Identified: {{ $json.need_identified }}\nTimeline Defined: {{ $json.timeline_defined }}\nBANT Summary: {{ $json.bant_summary }}\nAppointment Requested: {{ $json.appointment_requested }}\nPreferred Appointment Time: {{ $json.preferred_time }}\nObjections Raised: {{ JSON.stringify($json.detected_objections) }}\nObjection Handling Technique Selected: {{ $json.objection_technique }}\nObjection Response Script: {{ $json.objection_response_script }}\n\nWrite a professional CRM activity note for this inbound voice AI sales call.",
        "options": {
          "systemMessage": "You are a professional CRM administrator and sales operations specialist with expertise in Salesforce, HubSpot, and GoHighLevel CRM systems. Your task is to write concise, structured, professional activity notes for sales CRM systems.\n\nThe CRM note MUST follow this exact structure:\n\nCALL SUMMARY\n[1-2 sentences describing what happened on the call]\n\nQUALIFICATION STATUS\nBANT Score: X/10\n- Budget: [status and detail]\n- Authority: [status and detail]\n- Need: [status and detail]\n- Timeline: [status and detail]\n\nNEXT STEPS\n[Specific actionable items with dates/times where mentioned. Use bullet points.]\n\nOBJECTIONS ON FILE\n[List each objection. Use bullet points.]\n\nRECOMMENDED APPROACH\n[Which technique to use and brief rationale. 1-2 sentences.]\n\nTotal note length: 150-250 words. Use third person (e.g., 'Prospect confirmed...', 'Lead indicated...'). Be specific and actionable \u2014 any sales rep should be able to pick up this lead and know exactly what to do next.\n\nAlways respond with valid JSON matching the output schema exactly. Do not wrap in markdown code blocks."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.8
    },
    {
      "id": "ae2993f0-aa6c-4a9b-aaab-4f10caaef1f8",
      "name": "Gemini Flash (CRM Note)",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        34464,
        8128
      ],
      "parameters": {
        "options": {
          "temperature": 0.2,
          "maxOutputTokens": 800
        },
        "modelName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8956d772-8e28-4d96-ac8e-5d1a145b32fc",
      "name": "CRM Note Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        34768,
        8128
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"crm_note\": {\"type\": \"string\"}}, \"required\": [\"crm_note\"]}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "90320e2e-a46e-40d1-a8b8-8ca9fd52ca7d",
      "name": "Extract CRM Note",
      "type": "n8n-nodes-base.code",
      "position": [
        34880,
        7920
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst item = items[0];\n\nlet note;\ntry {\n  const raw = item.json.output || item.json;\n  if (typeof raw === 'string') {\n    const cleaned = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n    note = JSON.parse(cleaned);\n  } else if (typeof raw === 'object' && raw.crm_note !== undefined) {\n    note = raw;\n  } else if (raw.output && typeof raw.output === 'object') {\n    note = raw.output;\n  } else {\n    note = { crm_note: 'Voice AI inbound call \u2014 see transcript for details.' };\n  }\n} catch(e) {\n  note = { crm_note: 'Voice AI inbound call \u2014 see transcript for details.' };\n}\n\nconst prev = $('Extract Objection Response').item.json;\n\nreturn [{\n  json: {\n    ...prev,\n    crm_note: note.crm_note || 'Voice AI call logged.'\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "60f7c53e-fa87-4abb-814c-a5a141b6d2f4",
      "name": "Update Contact Notes",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        35040,
        7920
      ],
      "parameters": {
        "contactId": "={{ $json.ghl_contact_id }}",
        "operation": "update",
        "updateFields": {
          "tags": "voice-lead,qualified,hot"
        },
        "requestOptions": {}
      },
      "typeVersion": 2
    },
    {
      "id": "31d77ea6-aa77-446d-933a-27fb25e20cc2",
      "name": "Update Stage: Hot Lead",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        35280,
        7920
      ],
      "parameters": {
        "resource": "opportunity",
        "operation": "update",
        "updateFields": {
          "stageId": "YOUR_HOT_STAGE_ID"
        },
        "opportunityId": "={{ $json.ghl_opp_id }}",
        "requestOptions": {}
      },
      "typeVersion": 2
    },
    {
      "id": "16c3ca21-35f0-4707-8830-01f9abf439da",
      "name": "Log to Supabase",
      "type": "n8n-nodes-base.supabase",
      "position": [
        35520,
        7920
      ],
      "parameters": {
        "tableId": "voice_call_logs",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "call_id",
              "fieldValue": "={{ $json.call_id }}"
            },
            {
              "fieldId": "phone_number",
              "fieldValue": "={{ $json.phone_number }}"
            },
            {
              "fieldId": "direction",
              "fieldValue": "inbound"
            },
            {
              "fieldId": "provider",
              "fieldValue": "={{ $json.provider }}"
            },
            {
              "fieldId": "duration_sec",
              "fieldValue": "={{ $json.duration_sec }}"
            },
            {
              "fieldId": "transcript",
              "fieldValue": "={{ $json.transcript }}"
            },
            {
              "fieldId": "recording_url",
              "fieldValue": "={{ $json.recording_url }}"
            },
            {
              "fieldId": "bant_score",
              "fieldValue": "={{ $json.bant_score }}"
            },
            {
              "fieldId": "qualified",
              "fieldValue": "={{ $json.qualified }}"
            },
            {
              "fieldId": "budget_confirmed",
              "fieldValue": "={{ $json.budget_confirmed }}"
            },
            {
              "fieldId": "authority_confirmed",
              "fieldValue": "={{ $json.authority_confirmed }}"
            },
            {
              "fieldId": "need_identified",
              "fieldValue": "={{ $json.need_identified }}"
            },
            {
              "fieldId": "timeline_defined",
              "fieldValue": "={{ $json.timeline_defined }}"
            },
            {
              "fieldId": "bant_summary",
              "fieldValue": "={{ $json.bant_summary }}"
            },
            {
              "fieldId": "key_objections",
              "fieldValue": "={{ JSON.stringify($json.key_objections) }}"
            },
            {
              "fieldId": "appointment_requested",
              "fieldValue": "={{ $json.appointment_requested }}"
            },
            {
              "fieldId": "preferred_time",
              "fieldValue": "={{ $json.preferred_time }}"
            },
            {
              "fieldId": "ghl_contact_id",
              "fieldValue": "={{ $json.ghl_contact_id }}"
            },
            {
              "fieldId": "ghl_opp_id",
              "fieldValue": "={{ $json.ghl_opp_id }}"
            },
            {
              "fieldId": "crm_note",
              "fieldValue": "={{ $json.crm_note }}"
            },
            {
              "fieldId": "objection_response",
              "fieldValue": "={{ $json.objection_response_script }}"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c118faa4-789a-454e-a1cd-7f3d8cd0ef1a",
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        35760,
        7920
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $now.toISO() }}",
            "Call ID": "={{ $json.call_id }}",
            "CRM Note": "={{ $json.crm_note }}",
            "Provider": "={{ $json.provider }}",
            "Direction": "inbound",
            "Qualified": "={{ $json.qualified }}",
            "BANT Score": "={{ $json.bant_score }}",
            "GHL Opp ID": "={{ $json.ghl_opp_id }}",
            "Phone Number": "={{ $json.phone_number }}",
            "Duration (sec)": "={{ $json.duration_sec }}",
            "GHL Contact ID": "={{ $json.ghl_contact_id }}",
            "Objection Response": "={{ $json.objection_response_script }}",
            "Appointment Requested": "={{ $json.appointment_requested }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Voice Call Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "596534cd-65a3-42da-8348-819a0edfe61a",
      "name": "Search GHL Contact NQ",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        32400,
        8416
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "query": "={{ $json.phone_number }}"
        },
        "options": {},
        "operation": "getAll",
        "requestOptions": {}
      },
      "typeVersion": 2
    },
    {
      "id": "8a3a87b6-7f53-4d25-a7a1-38f01e077ac3",
      "name": "Contact Exists NQ?",
      "type": "n8n-nodes-base.if",
      "position": [
        32640,
        8416
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-nq-contact",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a7374849-8c33-427a-8e12-f895f811d18a",
      "name": "Update Contact NQ",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        32880,
        8288
      ],
      "parameters": {
        "contactId": "={{ $json.id }}",
        "operation": "update",
        "updateFields": {
          "tags": "voice-lead,nurturing"
        },
        "requestOptions": {}
      },
      "typeVersion": 2
    },
    {
      "id": "a202a62e-3393-43da-b585-e7169f30175f",
      "name": "Create Contact NQ",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        32880,
        8560
      ],
      "parameters": {
        "phone": "={{ $('Extract BANT').item.json.phone_number }}",
        "requestOptions": {},
        "additionalFields": {
          "tags": "voice-lead,nurturing",
          "source": "Voice AI",
          "firstName": "Voice Lead"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "89c14e26-81fb-4b90-a80a-267583429d40",
      "name": "Merge Contact NQ",
      "type": "n8n-nodes-base.merge",
      "position": [
        33120,
        8416
      ],
      "parameters": {
        "mode": "chooseBranch"
      },
      "typeVersion": 3
    },
    {
      "id": "1cf22c70-82c5-4fbd-9f0e-c96e5bf67246",
      "name": "Create Nurturing Opportunity",
      "type": "n8n-nodes-base.highLevel",
      "position": [
        33360,
        8416
      ],
      "parameters": {
        "name": "={{ 'Voice Lead (Nurturing) \u2014 ' + $('Extract BANT').item.json.phone_number }}",
        "resource": "opportunity",
        "contactId": "={{ $json.id }}",
        "pipelineId": "YOUR_PIPELINE_ID",
        "requestOptions": {},
        "additionalFields": {
          "stageId": "YOUR_NURTURING_STAGE_ID",
          "monetaryValue": 0
        }
      },
      "typeVersion": 2
    },
    {
      "id": "6c911ccd-41ec-4359-9b5b-dfbc985f754c",
      "name": "Trigger Nurture Workflow",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        33600,
        8416
      ],
      "parameters": {
        "url": "=https://services.leadconnectorhq.com/contacts/{{ $json.id }}/workflow/YOUR_NURTURE_WORKFLOW_ID",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendBody": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {}
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.GHL_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Version",
              "value": "2021-04-15"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "525f27f8-5a33-45d3-942d-a9c312f1a073",
      "name": "Log to Supabase NQ",
      "type": "n8n-nodes-base.supabase",
      "position": [
        33840,
        8416
      ],
      "parameters": {
        "tableId": "voice_call_logs",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "call_id",
              "fieldValue": "={{ $('Extract BANT').item.json.call_id }}"
            },
            {
              "fieldId": "phone_number",
              "fieldValue": "={{ $('Extract BANT').item.json.phone_number }}"
            },
            {
              "fieldId": "direction",
              "fieldValue": "inbound"
            },
            {
              "fieldId": "provider",
              "fieldValue": "={{ $('Extract BANT').item.json.provider }}"
            },
            {
              "fieldId": "duration_sec",
              "fieldValue": "={{ $('Extract BANT').item.json.duration_sec }}"
            },
            {
              "fieldId": "transcript",
              "fieldValue": "={{ $('Extract BANT').item.json.transcript }}"
            },
            {
              "fieldId": "recording_url",
              "fieldValue": "={{ $('Extract BANT').item.json.recording_url }}"
            },
            {
              "fieldId": "bant_score",
              "fieldValue": "={{ $('Extract BANT').item.json.bant_score }}"
            },
            {
              "fieldId": "qualified",
              "fieldValue": "false"
            },
            {
              "fieldId": "budget_confirmed",
              "fieldValue": "={{ $('Extract BANT').item.json.budget_confirmed }}"
            },
            {
              "fieldId": "authority_confirmed",
              "fieldValue": "={{ $('Extract BANT').item.json.authority_confirmed }}"
            },
            {
              "fieldId": "need_identified",
              "fieldValue": "={{ $('Extract BANT').item.json.need_identified }}"
            },
            {
              "fieldId": "timeline_defined",
              "fieldValue": "={{ $('Extract BANT').item.json.timeline_defined }}"
            },
            {
              "fieldId": "bant_summary",
              "fieldValue": "={{ $('Extract BANT').item.json.bant_summary }}"
            },
            {
              "fieldId": "key_objections",
              "fieldValue": "={{ JSON.stringify($('Extract BANT').item.json.key_objections) }}"
            },
            {
              "fieldId": "ghl_contact_id",
              "fieldValue": "={{ $json.id }}"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d35df5f0-9f24-4fe7-80c3-1b7414ee8182",
      "name": "Log to Google Sheets NQ",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        34080,
        8416
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $now.toISO() }}",
            "Call ID": "={{ $('Extract BANT').item.json.call_id }}",
            "CRM Note": "Not qualified \u2014 added to nurturing pipeline",
            "Provider": "={{ $('Extract BANT').item.json.provider }}",
            "Direction": "inbound",
            "Qualified": "false",
            "BANT Score": "={{ $('Extract BANT').item.json.bant_score }}",
            "GHL Opp ID": "",
            "Phone Number": "={{ $('Extract BANT').item.json.phone_number }}",
            "Duration (sec)": "={{ $('Extract BANT').item.json.duration_sec }}",
            "GHL Contact ID": "={{ $json.id }}",
            "Objection Response": "",
            "Appointment Requested": "false"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Voice Call Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "273af585-e14a-400c-b0a3-b602d0c9a0d7",
      "name": "_Inbound Call Webhook",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        30928,
        7792
      ],
      "parameters": {
        "width": 230,
        "height": 96,
        "content": "Receives end-of-call report from Vapi or Retell \u2014 auto 200 response"
      },
      "typeVersion": 1
    },
    {
      "id": "e48837f4-ed25-46d3-9340-29dd48d67cee",
      "name": "_Normalize Call Payload",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31216,
        7792
      ],
      "parameters": {
        "width": 202,
        "height": 96,
        "content": "Normalises Vapi + Retell payloads \u2192 unified: call_id, phone, transcript, duration_sec, provider"
      },
      "typeVersion": 1
    },
    {
      "id": "29735a4b-b331-4f46-a76e-6d6b58b4d67d",
      "name": "_Valid Call?",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31424,
        7808
      ],
      "parameters": {
        "width": 234,
        "height": 80,
        "content": "Skip calls < 30 s or transcript < 50 chars"
      },
      "typeVersion": 1
    },
    {
      "id": "0d851d7f-44da-4e10-a294-d74f4662190e",
      "name": "_BANT Qualifier",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31680,
        7792
      ],
      "parameters": {
        "width": 230,
        "height": 80,
        "content": "Claude Haiku scores Budget \u00b7 Authority \u00b7 Need \u00b7 Timeline (0\u201310) from transcript"
      },
      "typeVersion": 1
    },
    {
      "id": "76d160e0-f72d-475d-b9c7-1509de00ad66",
      "name": "_Extract BANT",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31936,
        7776
      ],
      "parameters": {
        "width": 186,
        "height": 80,
        "content": "Merges BANT score + objections into working object"
      },
      "typeVersion": 1
    },
    {
      "id": "a6373e91-8e9c-48ec-843a-9c8cbf8565b0",
      "name": "_Qualified?",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32160,
        7792
      ],
      "parameters": {
        "width": 201,
        "height": 80,
        "content": "score \u2265 6 \u2192 Booking path  |  score < 6 \u2192 Nurture path"
      },
      "typeVersion": 1
    },
    {
      "id": "736f9818-dd92-477d-9ae2-f9f12cc27778",
      "name": "_Booking Intent Detector",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32400,
        7776
      ],
      "parameters": {
        "width": 214,
        "height": 96,
        "content": "GPT-4o detects appointment intent + preferred time + confidence"
      },
      "typeVersion": 1
    },
    {
      "id": "9ef9c75b-a8bc-4df2-b599-d89048d851ce",
      "name": "_Extract Booking",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32640,
        7792
      ],
      "parameters": {
        "width": 230,
        "height": 80,
        "content": "Extracts appointment_requested, preferred_time, objections[]"
      },
      "typeVersion": 1
    },
    {
      "id": "47aa31eb-49b6-4015-b19d-816b202d4333",
      "name": "_Search GHL Contact",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32880,
        7808
      ],
      "parameters": {
        "width": 201,
        "height": 80,
        "content": "Search HighLevel contact by phone number"
      },
      "typeVersion": 1
    },
    {
      "id": "32f39581-4a6b-400b-9060-0da06a47a6c1",
      "name": "_Contact Exists?",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        33104,
        7792
      ],
      "parameters": {
        "width": 202,
        "height": 80,
        "content": "TRUE \u2192 update existing  |  FALSE \u2192 create new"
      },
      "typeVersion": 1
    },
    {
      "id": "4277fbd1-83ed-4bb0-a628-4303683e4b2a",
      "name": "_Update Contact",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        33328,
        7664
      ],
      "parameters": {
        "width": 218,
        "height": 80,
        "content": "Tag existing HL contact: voice-lead, qualified"
      },
      "typeVersion": 1
    },
    {
      "id": "7c9b4dc1-59c9-4164-bfb6-a08d3500df98",
      "name": "_Create Contact",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        33296,
        7936
      ],
      "parameters": {
        "width": 250,
        "height": 60,
        "content": "Create HL contact with phone + source = Voice AI"
      },
      "typeVersion": 1
    },
    {
      "id": "7729297d-f487-4fe0-be94-0f65a6f44472",
      "name": "_Merge Contact Paths",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        33568,
        7792
      ],
      "parameters": {
        "width": 233,
        "height": 80,
        "content": "Rejoin update + create branches with resolved contact ID"
      },
      "typeVersion": 1
    },
    {
      "id": "62634ea8-188f-4d28-ac13-ee2fa60e5871",
      "name": "_Create Hot Opportunity",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        33808,
        7776
      ],
      "parameters": {
        "width": 233,
        "height": 112,
        "content": "Open opportunity in Hot Lead pipeline stage"
      },
      "typeVersion": 1
    },
    {
      "id": "98cf05a0-3247-421b-b81c-afb7c80e47c5",
      "name": "_Objection Handler",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        34064,
        7792
      ],
      "parameters": {
        "width": 250,
        "height": 112,
        "content": "Claude Sonnet generates rebuttal script using feel-felt-found / Challenger Sale"
      },
      "typeVersion": 1
    },
    {
      "id": "54fd9f1b-3439-4fec-ad78-9c51c9bb3295",
      "name": "_Extract Objection Response",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        34352,
        7792
      ],
      "parameters": {
        "width": 233,
        "height": 80,
        "content": "Extracts response_script, technique, confidence"
      },
      "typeVersion": 1
    },
    {
      "id": "e1ba0db1-3bc4-411b-94d7-b847986ad2cf",
      "name": "_CRM Note Writer",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        34608,
        7824
      ],
      "parameters": {
        "width": 214,
        "height": 80,
        "content": "Gemini 2.0 Flash writes professional CRM note from call summary"
      },
      "typeVersion": 1
    },
    {
      "id": "b1d923f6-de29-421a-8cb8-f1ce29a9506d",
      "name": "_Extract CRM Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        34848,
        7824
      ],
      "parameters": {
        "width": 166,
        "height": 80,
        "content": "Extracts crm_note string from agent output"
      },
      "typeVersion": 1
    },
    {
      "id": "ec400620-50e3-480d-8688-d5635eba682e",
      "name": "_Update Contact Notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        35056,
        7808
      ],
      "parameters": {
        "width": 218,
        "height": 80,
        "content": "Append AI CRM note to HL contact record"
      },
      "typeVersion": 1
    },
    {
      "id": "906c7b58-4f9e-45f3-ad43-8700d2dc18d4",
      "name": "_Update Stage: Hot Lead",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        35312,
        7808
      ],
      "parameters": {
        "width": 186,
        "height": 80,
        "content": "Move HL opportunity to Hot Lead stage"
      },
      "typeVersion": 1
    },
    {
      "id": "d2b1f2d8-58f9-49b2-98e9-241a12cb3af5",
      "name": "_Log to Supabase",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        35536,
        7792
      ],
      "parameters": {
        "width": 217,
        "height": 80,
        "content": "Persist qualified inbound call to voice_call_logs"
      },
      "typeVersion": 1
    },
    {
      "id": "78d3b086-17b7-4a5f-ab53-b5331840941a",
      "name": "_Log to Google Sheets",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        35760,
        7792
      ],
      "parameters": {
        "width": 265,
        "height": 60,
        "content": "Append summary row to Voice Call Log sheet"
      },
      "typeVersion": 1
    },
    {
      "id": "8e433858-ec9b-48eb-92ec-580c9779f63c",
      "name": "_Search GHL Contact NQ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32400,
        8288
      ],
      "parameters": {
        "width": 265,
        "height": 60,
        "content": "Search HL contact by phone before nurture upsert"
      },
      "typeVersion": 1
    },
    {
      "id": "d90018f8-a85d-49e0-8e60-4197bce822b0",
      "name": "_Contact Exists NQ?",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32640,
        8288
      ],
      "parameters": {
        "width": 220,
        "height": 60,
        "content": "TRUE \u2192 update  |  FALSE \u2192 create"
      },
      "typeVersion": 1
    },
    {
      "id": "41168042-c6b6-4b2f-b6a7-3f0b3b9f0d9f",
      "name": "_Update Contact NQ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32880,
        8160
      ],
      "parameters": {
        "width": 265,
        "height": 60,
        "content": "Tag contact: not-qualified, add to nurture queue"
      },
      "typeVersion": 1
    },
    {
      "id": "606e124d-ad07-4b4c-9826-b1f2ce358106",
      "name": "_Create Contact NQ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32816,
        8448
      ],
      "parameters": {
        "width": 250,
        "height": 60,
        "content": "Create HL contact, flag for nurture sequence"
      },
      "typeVersion": 1
    },
    {
      "id": "33642913-5e00-43e0-9e22-4033e9d8b7fe",
      "name": "_Merge Contact NQ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        33088,
        8320
      ],
      "parameters": {
        "width": 218,
        "height": 80,
        "content": "Rejoin upsert paths with resolved contact ID"
      },
      "typeVersion": 1
    },
    {
      "id": "dbccd6fa-0fe5-4de9-af2c-5a0f69bb67c1",
      "name": "_Create Nurturing Opportunity",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        33328,
        8320
      ],
      "parameters": {
        "width": 201,
        "height": 80,
        "content": "Open opportunity in Nurturing pipeline stage"
      },
      "typeVersion": 1
    },
    {
      "id": "8e42d98d-6440-4514-8b34-00a608927d10",
      "name": "_Trigger Nurture Workflow",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        33552,
        8304
      ],
      "parameters": {
        "width": 262,
        "height": 80,
        "content": "Fire HL automation to enrol contact in nurture email/SMS sequence"
      },
      "typeVersion": 1
    },
    {
      "id": "1b18856b-9492-41a1-bcfe-4fac1e1fa12f",
      "name": "_Log to Supabase NQ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        33840,
        8304
      ],
      "parameters": {
        "width": 234,
        "height": 80,
        "content": "Persist not-qualified call to voice_call_logs"
      },
      "typeVersion": 1
    },
    {
      "id": "1b82904b-de99-4249-8a92-8e4d575cc0c7",
      "name": "_Log to Google Sheets NQ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        34112,
        8304
      ],
      "parameters": {
        "width": 188,
        "height": 80,
        "content": "Append not-qualified row to sheet"
      },
      "typeVersion": 1
    },
    {
      "id": "bbd0c5b6-a5db-486d-b6b4-c266f2fd9e34",
      "name": "_Daily Outbound Schedule",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        30960,
        8800
      ],
      "parameters": {
        "width": 220,
        "height": 60,
        "content": "9:00 AM Mon\u2013Fri \u00b7 cron: 0 9 * * 1-5"
      },
      "typeVersion": 1
    },
    {
      "id": "524fc730-14d3-41f7-9e65-4ed9f44ac6af",
      "name": "_Fetch GHL Nurturing Leads",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31200,
        8800
      ],
      "parameters": {
        "width": 201,
        "height": 80,
        "content": "Retrieve all Nurturing stage contacts from HighLevel"
      },
      "typeVersion": 1
    },
    {
      "id": "cd18d91b-4023-4bf7-a8d3-03efdef41a18",
      "name": "_Priority Ranker",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31408,
        8800
      ],
      "parameters": {
        "width": 182,
        "height": 96,
        "content": "GPT-4o Mini ranks leads by conversion priority to maximise call ROI"
      },
      "typeVersion": 1
    },
    {
      "id": "3e425993-f919-4492-8fc8-e3a5e7b9b268",
      "name": "_Extract Ranked Leads",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31648,
        8768
      ],
      "parameters": {
        "width": 150,
        "height": 80,
        "content": "Sort ranked_leads by priority_score desc \u00b7 limit 50"
      },
      "typeVersion": 1
    },
    {
      "id": "b8f5582d-0b53-4191-862f-ec917946940b",
      "name": "_Split in Batches",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        31872,
        8816
      ],
      "parameters": {
        "width": 218,
        "height": 80,
        "content": "5 leads per batch \u2014 respects Vapi rate limits"
      },
      "typeVersion": 1
    },
    {
      "id": "3e9df9a7-72ea-4414-8d4b-431e9c037405",
      "name": "_Initiate Vapi Outbound Call",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32128,
        8768
      ],
      "parameters": {
        "width": 214,
        "height": 80,
        "content": "POST outbound call to Vapi API for each prioritised lead"
      },
      "typeVersion": 1
    },
    {
      "id": "24cc9898-1423-4517-b2fc-278a130bdb69",
      "name": "_Log
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

How this works

This workflow qualifies inbound voice sales calls in real time and routes them into HighLevel for follow-up. It listens for webhook triggers from a phone system, then uses Claude, GPT-4o and Gemini to analyse the conversation, extract lead details, and decide whether the prospect meets qualification criteria. The structured output feeds directly into HighLevel to create or update contacts and opportunities without manual data entry.

Use it when you run high-volume inbound sales calls and need consistent qualification before human follow-up. Skip it if your calls are purely transactional or if you require offline processing. A common variation replaces the multi-model analysis with a single provider for simpler deployments.

About this workflow

⏺ πŸš€ How it works

Source: https://n8n.io/workflows/14169/ β€” original creator credit. Request a take-down β†’

More AI & RAG workflows β†’ Β· Browse all categories β†’

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Tired of grinding out YouTube content? This n8n workflow turns AI into your personal video factoryβ€”creating engaging, faceless shorts on autopilot. Perfect for creators, marketers, or side-hustlers lo

HTTP Request, Google Drive, Google Sheets +6
AI & RAG

Faceless YouTube Generator. Uses httpRequest, limit, googleDrive, googleSheets. Webhook trigger; 49 nodes.

HTTP Request, Google Drive, Google Sheets +7
AI & RAG

Resume Screening & Behavioral Interviews with Gemini, Elevenlabs, & Notion ATS copy. Uses outputParserStructured, chainLlm, googleDrive, stickyNote. Webhook trigger; 67 nodes.

Output Parser Structured, Chain Llm, Google Drive +9
AI & RAG

Candidate Engagement | Resume Screening | AI Voice Interviews | Applicant Insights

Output Parser Structured, Chain Llm, Google Drive +9
AI & RAG

The Multi-Model Agency Content Engine is a high-performance editorial system designed for agencies. It solves the "blank page" problem by alternating between real-world social proof and strategic expe

Google Sheets, Gmail, Google Drive +6