GoHighLevel to n8n Bridge
Module 14 · business · intermediate
starter.json
In n8n, copy this file and paste it onto the canvas, or use Import from File.
{
"name": "14 - GoHighLevel to n8n Bridge (starter)",
"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": {
"content": "## Your task\n1. Send a test lead with `?dry_run=true` and read **Rule Score**'s output: score + signals.\n2. Add **Dry Run?** → **AI Qualify** (Information Extractor + OpenAI) scoring fit against the offer in Settings.\n3. **Combine Scores**: 40% rules + 60% AI → tier (hot/warm/cold), tag and a CRM note.\n4. Unless dry run: **HTTP Request** POST `/contacts/{id}/tags` and `/contacts/{id}/notes` on the LeadConnector API (Header Auth credential + `Version: 2021-07-28`).\n5. Respond with the result; alert sales on hot leads.\n\nTest: `./test/test.sh`",
"height": 260,
"width": 420
},
"id": "starter-task-note",
"name": "Your Task",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
0,
-340
]
}
],
"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": [
[]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
}
}