Skip to content

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"
  }
}