This workflow follows the Airtable → HTTP Request 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 →
{
"nodes": [
{
"id": "sticky-title",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-880,
-440
],
"parameters": {
"content": "## Every lead source into one CRM\n- Facebook ads, the website form, and Google lead forms all end up as one clean contact in GoHighLevel, with every lead logged in Airtable\n- Repeat enquiries update the contact already on file, and the sales team gets a Slack message either way",
"height": 200,
"width": 560,
"color": 5
}
},
{
"id": "sticky-sources",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1200,
-200
],
"parameters": {
"content": "## Three ways in, one path\n- Facebook, the website, and Google each post to their own door here\n- Past this point every lead gets treated exactly the same",
"height": 780,
"width": 340,
"color": 4
}
},
{
"id": "webhook-facebook",
"name": "Facebook lead arrives",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-1120,
-40
],
"parameters": {
"httpMethod": "POST",
"path": "demo/lead-facebook",
"responseMode": "responseNode",
"options": {}
}
},
{
"id": "webhook-website",
"name": "Website form lead arrives",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-1120,
180
],
"parameters": {
"httpMethod": "POST",
"path": "demo/lead-website",
"responseMode": "responseNode",
"options": {}
}
},
{
"id": "webhook-google",
"name": "Google lead arrives",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-1120,
400
],
"parameters": {
"httpMethod": "POST",
"path": "demo/lead-google",
"responseMode": "responseNode",
"options": {}
}
},
{
"id": "code-read-lead",
"name": "Read the lead details",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-820,
180
],
"parameters": {
"jsCode": "// Three sources post here. Work out which one sent the lead and pull the same clean details out every time.\nconst raw = $json.body ?? $json;\nconst url = String($json.webhookUrl || '');\n\nconst clean = (v) => (v === undefined || v === null ? '' : String(v).trim());\nconst pick = (map, ...keys) => {\n for (const k of keys) {\n const v = clean(map[k]);\n if (v) return v;\n }\n return '';\n};\n\nconst lead = {\n name: '',\n phone: '',\n email: '',\n service: '',\n message: '',\n lead_source: 'website',\n};\n\n// Facebook Lead Ads sends the answers as a field_data list\nconst fbFields =\n raw?.entry?.[0]?.changes?.[0]?.value?.field_data ??\n raw?.field_data ??\n null;\n\n// Google lead forms (and Zapier forwards of them) send a user_column_data list\nconst gFields = raw?.user_column_data ?? raw?.lead?.user_column_data ?? null;\n\nif (Array.isArray(fbFields)) {\n lead.lead_source = 'facebook';\n const map = {};\n for (const f of fbFields) {\n map[clean(f?.name).toLowerCase()] = clean((f?.values || [])[0]);\n }\n lead.name =\n pick(map, 'full_name', 'name') ||\n [pick(map, 'first_name'), pick(map, 'last_name')].filter(Boolean).join(' ');\n lead.email = pick(map, 'email');\n lead.phone = pick(map, 'phone_number', 'phone');\n lead.service = pick(map, 'service', 'what_do_you_need_help_with', 'job_type');\n lead.message = pick(map, 'message', 'comments', 'details');\n} else if (Array.isArray(gFields)) {\n lead.lead_source = 'google';\n const map = {};\n for (const c of gFields) {\n const key = clean(c?.column_id || c?.column_name).toLowerCase();\n if (key && !map[key]) map[key] = clean(c?.string_value);\n }\n lead.name =\n pick(map, 'full_name', 'name') ||\n [pick(map, 'first_name'), pick(map, 'last_name')].filter(Boolean).join(' ');\n lead.email = pick(map, 'email', 'user_email');\n lead.phone = pick(map, 'phone_number', 'user_phone', 'phone');\n lead.service = pick(map, 'service', 'which_service_do_you_need', 'job_type');\n lead.message = pick(map, 'message', 'comments', 'details');\n} else {\n // Website form, either flat fields or wrapped in a data object\n const flat = raw?.data && typeof raw.data === 'object' ? raw.data : raw;\n const map = {};\n for (const [k, v] of Object.entries(flat || {})) {\n map[clean(k).toLowerCase()] = clean(v);\n }\n lead.name =\n pick(map, 'name', 'full_name', 'fullname') ||\n [pick(map, 'first_name', 'firstname'), pick(map, 'last_name', 'lastname')]\n .filter(Boolean)\n .join(' ');\n lead.email = pick(map, 'email', 'email_address');\n lead.phone = pick(map, 'phone', 'phone_number', 'mobile', 'tel');\n lead.service = pick(map, 'service', 'service_needed', 'job_type');\n lead.message = pick(map, 'message', 'comments', 'details', 'notes');\n // A forwarded Google lead can arrive flat too, so let the door it came through decide\n if (url.includes('lead-google')) lead.lead_source = 'google';\n if (url.includes('lead-facebook')) lead.lead_source = 'facebook';\n}\n\nif (!lead.name) lead.name = 'New lead';\n\n// Phone down to just digits with a leading +\nlet digits = lead.phone.replace(/[^0-9]/g, '');\nif (digits.length === 10) digits = '1' + digits;\nlead.phone = digits ? '+' + digits : '';\n\nconst tagBySource = {\n facebook: 'fb-lead',\n website: 'website-lead',\n google: 'google-lead',\n};\nlead.source_tag = tagBySource[lead.lead_source];\nlead.receivedAt = new Date().toISOString();\n\nreturn [{ json: lead }];"
}
},
{
"id": "http-ghl-check",
"name": "Check GoHighLevel for this customer",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-580,
180
],
"parameters": {
"method": "GET",
"url": "https://services.leadconnectorhq.com/contacts/",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "locationId",
"value": "bsHomeSvcDemo01"
},
{
"name": "query",
"value": "={{ $json.phone || $json.email }}"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
}
]
},
"options": {}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"id": "if-existing",
"name": "Already in GoHighLevel?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-340,
180
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "has-contact",
"leftValue": "={{ ($json.contacts || []).length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "http-ghl-update",
"name": "Update the customer we already have",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-80,
60
],
"parameters": {
"method": "POST",
"url": "https://services.leadconnectorhq.com/contacts/upsert",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ locationId: 'bsHomeSvcDemo01', name: $('Read the lead details').item.json.name, phone: $('Read the lead details').item.json.phone, email: $('Read the lead details').item.json.email, source: $('Read the lead details').item.json.lead_source, tags: [$('Read the lead details').item.json.source_tag, 'repeat-enquiry'] }) }}",
"options": {}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"id": "http-ghl-create",
"name": "Save the new lead in GoHighLevel",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-80,
320
],
"parameters": {
"method": "POST",
"url": "https://services.leadconnectorhq.com/contacts/upsert",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ locationId: 'bsHomeSvcDemo01', name: $('Read the lead details').item.json.name, phone: $('Read the lead details').item.json.phone, email: $('Read the lead details').item.json.email, source: $('Read the lead details').item.json.lead_source, tags: [$('Read the lead details').item.json.source_tag, 'new-lead'] }) }}",
"options": {}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"id": "airtable-log",
"name": "Log the lead in Airtable",
"type": "n8n-nodes-base.airtable",
"typeVersion": 2.1,
"position": [
180,
180
],
"parameters": {
"authentication": "airtableTokenApi",
"resource": "record",
"operation": "create",
"base": {
"__rl": true,
"mode": "id",
"value": "appBlueSummit001"
},
"table": {
"__rl": true,
"mode": "id",
"value": "tblLeads"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"Name": "={{ $('Read the lead details').item.json.name }}",
"Phone": "={{ $('Read the lead details').item.json.phone }}",
"Email": "={{ $('Read the lead details').item.json.email }}",
"Source": "={{ $('Read the lead details').item.json.lead_source }}",
"Service": "={{ $('Read the lead details').item.json.service }}",
"Status": "New"
},
"matchingColumns": [],
"schema": []
},
"options": {}
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
}
},
{
"id": "slack-notify",
"name": "Tell the sales team in Slack",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.3,
"position": [
420,
180
],
"parameters": {
"resource": "message",
"operation": "post",
"select": "channel",
"channelId": {
"__rl": true,
"value": "#sales-team",
"mode": "name"
},
"text": "=New lead: {{ $('Read the lead details').item.json.name }}\nCame from: {{ $('Read the lead details').item.json.lead_source }}\nWants: {{ $('Read the lead details').item.json.service || 'not sure yet' }}\nPhone: {{ $('Read the lead details').item.json.phone || 'no number given' }}",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
}
},
{
"id": "respond-ok",
"name": "Tell the lead source it landed",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
660,
180
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ saved: true, source: $('Read the lead details').item.json.lead_source }) }}",
"options": {}
}
}
],
"connections": {
"Facebook lead arrives": {
"main": [
[
{
"node": "Read the lead details",
"type": "main",
"index": 0
}
]
]
},
"Website form lead arrives": {
"main": [
[
{
"node": "Read the lead details",
"type": "main",
"index": 0
}
]
]
},
"Google lead arrives": {
"main": [
[
{
"node": "Read the lead details",
"type": "main",
"index": 0
}
]
]
},
"Read the lead details": {
"main": [
[
{
"node": "Check GoHighLevel for this customer",
"type": "main",
"index": 0
}
]
]
},
"Check GoHighLevel for this customer": {
"main": [
[
{
"node": "Already in GoHighLevel?",
"type": "main",
"index": 0
}
]
]
},
"Already in GoHighLevel?": {
"main": [
[
{
"node": "Update the customer we already have",
"type": "main",
"index": 0
}
],
[
{
"node": "Save the new lead in GoHighLevel",
"type": "main",
"index": 0
}
]
]
},
"Update the customer we already have": {
"main": [
[
{
"node": "Log the lead in Airtable",
"type": "main",
"index": 0
}
]
]
},
"Save the new lead in GoHighLevel": {
"main": [
[
{
"node": "Log the lead in Airtable",
"type": "main",
"index": 0
}
]
]
},
"Log the lead in Airtable": {
"main": [
[
{
"node": "Tell the sales team in Slack",
"type": "main",
"index": 0
}
]
]
},
"Tell the sales team in Slack": {
"main": [
[
{
"node": "Tell the lead source it landed",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
airtableTokenApihttpHeaderAuthslackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Webhook-Intake-Normalize-Crm. Uses httpRequest, airtable, slack. Webhook trigger; 13 nodes.
Source: https://github.com/mcruz1799/automation-examples/blob/main/claude-code/02-automation-file-factory/skills/n8n/section-templates/webhook-intake-normalize-crm.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This guide will walk you through setting up your n8n workflow. By the end, you'll have a fully automated system for managing your contract employee placements, from generating documents to sending ren
HR teams, IT Operations, and System Administrators managing employee onboarding at scale. It’s perfect if you use Odoo 18 to trigger account requests and need Redmine + GitLab accounts created instant
This workflow is a complete, production-ready solution for recovering abandoned carts in Shopify stores using a multi-channel, multi-touch approach. It automates personalized follow-ups via Email, SMS
Backbrief: transcripts (Zoom webhook -> Slack + vault). Uses httpRequest, slack. Webhook trigger; 52 nodes.
qualiopi. Uses airtable, telegram, emailSend, httpRequest. Webhook trigger; 51 nodes.