GoHighLevel to n8n Bridge
Module 14 · business · intermediate
learner.json
In n8n, copy this file and paste it onto the canvas, or use Import from File.
{
"name": "14 - GoHighLevel to n8n Bridge",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "ghl/lead",
"responseMode": "responseNode",
"options": {}
},
"id": "m14-webhook",
"name": "GHL Lead Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
200
],
"webhookId": "course-14-ghl-lead"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "s1",
"name": "ghl_base_url",
"value": "https://services.leadconnectorhq.com",
"type": "string"
},
{
"id": "s2",
"name": "ghl_api_version",
"value": "2021-07-28",
"type": "string"
},
{
"id": "s3",
"name": "hot_threshold",
"value": 70,
"type": "number"
},
{
"id": "s4",
"name": "warm_threshold",
"value": 40,
"type": "number"
},
{
"id": "s5",
"name": "hot_tag",
"value": "ai-hot",
"type": "string"
},
{
"id": "s6",
"name": "warm_tag",
"value": "ai-warm",
"type": "string"
},
{
"id": "s7",
"name": "cold_tag",
"value": "ai-cold",
"type": "string"
},
{
"id": "s8",
"name": "offer",
"value": "We are a digital marketing agency for local service businesses (dentists, contractors, clinics). We sell websites, Google Ads management and lead-follow-up automation, from $1,500/month. Ideal client: an established business with steady revenue that wants more booked jobs within the next 1–3 months.",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {}
},
"id": "m14-settings",
"name": "Settings (edit me)",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
200
]
},
{
"parameters": {
"jsCode": "// Accept both GoHighLevel outbound shapes and return one clean lead:\n// - \"Custom Webhook\" action: the body you define (recommended, see lesson Step 1)\n// - \"Webhook\" action: standard contact fields, your extra fields inside customData\nconst req = $input.first().json;\nconst b = req.body ?? {};\nconst cd = b.customData ?? {};\nconst pick = (...keys) => {\n for (const k of keys) {\n const v = cd[k] ?? b[k] ?? b.contact?.[k];\n if (v !== undefined && v !== null && String(v).trim() !== '') return String(v).trim();\n }\n return '';\n};\n\nconst first = pick('first_name', 'firstName');\nconst last = pick('last_name', 'lastName');\nconst tags = [].concat(b.tags ?? cd.tags ?? []).flatMap((t) => String(t).split(',')).map((t) => t.trim()).filter(Boolean);\n\nreturn [{\n json: {\n contact_id: pick('contact_id', 'contactId', 'id'),\n location_id: pick('location_id', 'locationId') || b.location?.id || '',\n name: pick('full_name', 'fullName', 'name') || [first, last].filter(Boolean).join(' '),\n first_name: first,\n email: pick('email').toLowerCase(),\n phone: pick('phone').replace(/[^\\d+]/g, ''),\n source: pick('source', 'contact_source', 'lead_source').toLowerCase(),\n message: pick('message', 'notes', 'details', 'how_can_we_help').slice(0, 3000),\n company: pick('company_name', 'companyName', 'company'),\n website: pick('website'),\n tags,\n dry_run: req.query?.dry_run === 'true',\n },\n}];\n"
},
"id": "m14-normalize",
"name": "Normalize GHL Payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
440,
200
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.contact_id }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "m14-has-id",
"name": "Has Contact ID?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
660,
200
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { error: \"contact_id is required: in GoHighLevel, map the Contact ID merge field into the webhook body\" } }}",
"options": {
"responseCode": 400
}
},
"id": "m14-400",
"name": "Respond Missing ID",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
880,
380
]
},
{
"parameters": {
"jsCode": "// Free, instant scoring from the data we already have (no AI, no API calls).\n// Each signal adds points; the AI step later refines this.\nconst FREE_MAIL = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com', 'aol.com', 'proton.me', 'protonmail.com', 'live.com', 'msn.com'];\nconst HIGH_INTENT_SOURCES = ['website form', 'referral', 'google ads', 'booking', 'quote request', 'contact form'];\n\nreturn $input.all().map(({ json: lead }) => {\n const signals = [];\n let score = 0;\n const add = (points, why) => { score += points; signals.push(`+${points} ${why}`); };\n\n const domain = lead.email.split('@')[1] ?? '';\n const businessEmail = domain !== '' && !FREE_MAIL.includes(domain);\n const text = `${lead.message} ${lead.company}`.toLowerCase();\n\n if (businessEmail) add(25, `business email (${domain})`);\n if (lead.phone.length >= 7) add(15, 'phone number');\n if (lead.company || lead.website) add(10, 'company or website given');\n if (lead.message.length >= 40) add(15, 'detailed message');\n if (HIGH_INTENT_SOURCES.includes(lead.source)) add(10, `high-intent source (${lead.source})`);\n if (/\\$\\s?\\d|budget|quote|pricing|price|cost|invest/.test(text)) add(15, 'mentions budget or pricing');\n if (/asap|urgent|this week|today|tomorrow|right away|immediately/.test(text)) add(10, 'urgency');\n\n return {\n json: {\n ...lead,\n company_domain: businessEmail ? domain : '',\n rule_score: Math.min(score, 100),\n rule_signals: signals,\n },\n };\n});\n"
},
"id": "m14-rules",
"name": "Rule Score",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
880,
120
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "d1",
"leftValue": "={{ $json.dry_run }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "m14-dry",
"name": "Dry Run?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1100,
120
]
},
{
"parameters": {
"text": "=OUR OFFER AND IDEAL CLIENT:\n{{ $('Settings (edit me)').first().json.offer }}\n\nNEW LEAD:\nName: {{ $json.name || 'unknown' }}\nCompany: {{ $json.company || $json.company_domain || 'unknown' }}\nWebsite: {{ $json.website || 'none' }}\nSource: {{ $json.source || 'unknown' }}\nExisting tags: {{ $json.tags.join(', ') || 'none' }}\nMessage: {{ $json.message || '(no message)' }}",
"attributes": {
"attributes": [
{
"name": "fit_score",
"type": "number",
"description": "0-100: how likely this lead becomes a paying client for OUR offer. 80+ = clear need, budget and timing; 40-79 = possible; below 40 = poor fit, spam, job seeker or vendor pitch.",
"required": true
},
{
"name": "intent",
"description": "One short phrase: what they want (e.g. 'new website + Google Ads')",
"required": true
},
{
"name": "budget_signal",
"description": "What the lead says or implies about budget, or 'unknown'",
"required": true
},
{
"name": "summary",
"description": "One sentence a salesperson can read before calling",
"required": true
},
{
"name": "next_step",
"description": "One concrete next action for the salesperson",
"required": true
}
]
},
"options": {
"systemPromptTemplate": "You qualify inbound sales leads for a small business. Be skeptical and concise. Only use facts given; never invent company details."
}
},
"id": "m14-ai",
"name": "AI Qualify",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"typeVersion": 1.2,
"position": [
1320,
220
]
},
{
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-5-mini",
"cachedResultName": "gpt-5-mini"
},
"options": {}
},
"id": "m14-model",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.3,
"position": [
1320,
440
]
},
{
"parameters": {
"jsCode": "// Blend rule score (40%) with the AI fit score (60%), pick a tier, and write the CRM note.\nconst s = $('Settings (edit me)').first().json;\n\nreturn $input.all().map((item, i) => {\n const lead = $('Rule Score').all()[i].json;\n const ai = item.json.output ?? {};\n const aiScore = Math.max(0, Math.min(100, Math.round(Number(ai.fit_score) || 0)));\n const score = lead.dry_run ? lead.rule_score : Math.round(0.4 * lead.rule_score + 0.6 * aiScore);\n const tier = score >= s.hot_threshold ? 'hot' : score >= s.warm_threshold ? 'warm' : 'cold';\n\n const note = [\n `🤖 AI lead qualification: ${tier.toUpperCase()} (${score}/100)`,\n ai.summary ? `Summary: ${ai.summary}` : null,\n ai.intent ? `Intent: ${ai.intent}` : null,\n ai.budget_signal ? `Budget: ${ai.budget_signal}` : null,\n ai.next_step ? `Suggested next step: ${ai.next_step}` : null,\n `Signals: ${lead.rule_signals.join(', ') || 'none'}`,\n lead.dry_run ? '' : `Scores: rules ${lead.rule_score}, AI ${aiScore}`,\n ].filter(Boolean).join('\\n');\n\n return {\n json: {\n contact_id: lead.contact_id,\n name: lead.name,\n email: lead.email,\n score,\n tier,\n tag: s[`${tier}_tag`],\n rule_score: lead.rule_score,\n ai_score: lead.dry_run ? null : aiScore,\n signals: lead.rule_signals,\n summary: ai.summary ?? null,\n next_step: ai.next_step ?? null,\n note,\n dry_run: lead.dry_run,\n },\n };\n});\n"
},
"id": "m14-combine",
"name": "Combine Scores",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1540,
120
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "w1",
"leftValue": "={{ $json.dry_run }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "m14-write",
"name": "Write to GHL?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1760,
120
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Settings (edit me)').first().json.ghl_base_url }}/contacts/{{ $json.contact_id }}/tags",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "={{ $('Settings (edit me)').first().json.ghl_api_version }}"
},
{
"name": "Accept",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ tags: [$json.tag, 'ai-qualified'] }) }}",
"options": {}
},
"id": "m14-tag",
"name": "Add Tag in GHL",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1980,
40
],
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Settings (edit me)').first().json.ghl_base_url }}/contacts/{{ $('Combine Scores').item.json.contact_id }}/notes",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "={{ $('Settings (edit me)').first().json.ghl_api_version }}"
},
{
"name": "Accept",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ body: $('Combine Scores').item.json.note }) }}",
"options": {}
},
"id": "m14-note",
"name": "Add Note in GHL",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2200,
40
],
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $('Combine Scores').item.json }}",
"options": {}
},
"id": "m14-respond",
"name": "Respond",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
2420,
120
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "h1",
"leftValue": "={{ $('Combine Scores').item.json.tier }}",
"rightValue": "hot",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "m14-hot",
"name": "Hot Lead?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
2420,
-100
]
},
{
"parameters": {},
"id": "m14-alert",
"name": "Alert Sales (Exercise 1)",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
2640,
-100
]
}
],
"connections": {
"GHL Lead Webhook": {
"main": [
[
{
"node": "Settings (edit me)",
"type": "main",
"index": 0
}
]
]
},
"Settings (edit me)": {
"main": [
[
{
"node": "Normalize GHL Payload",
"type": "main",
"index": 0
}
]
]
},
"Normalize GHL Payload": {
"main": [
[
{
"node": "Has Contact ID?",
"type": "main",
"index": 0
}
]
]
},
"Has Contact ID?": {
"main": [
[
{
"node": "Rule Score",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Missing ID",
"type": "main",
"index": 0
}
]
]
},
"Rule Score": {
"main": [
[
{
"node": "Dry Run?",
"type": "main",
"index": 0
}
]
]
},
"Dry Run?": {
"main": [
[
{
"node": "Combine Scores",
"type": "main",
"index": 0
}
],
[
{
"node": "AI Qualify",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Qualify",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"AI Qualify": {
"main": [
[
{
"node": "Combine Scores",
"type": "main",
"index": 0
}
]
]
},
"Combine Scores": {
"main": [
[
{
"node": "Write to GHL?",
"type": "main",
"index": 0
}
]
]
},
"Write to GHL?": {
"main": [
[
{
"node": "Add Tag in GHL",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond",
"type": "main",
"index": 0
}
]
]
},
"Add Tag in GHL": {
"main": [
[
{
"node": "Add Note in GHL",
"type": "main",
"index": 0
}
]
]
},
"Add Note in GHL": {
"main": [
[
{
"node": "Respond",
"type": "main",
"index": 0
},
{
"node": "Hot Lead?",
"type": "main",
"index": 0
}
]
]
},
"Hot Lead?": {
"main": [
[
{
"node": "Alert Sales (Exercise 1)",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
}
}