This workflow corresponds to n8n.io template #12374 — we link there as the canonical source.
This workflow follows the Google Sheets → 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": "defe3eb5-a816-498f-aa9f-db5497a1ae85",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"position": [
-1760,
576
],
"parameters": {
"path": "website-lead",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "7a3ef9e5-f744-40c9-8a5e-f6d499367f9e",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1872,
480
],
"parameters": {
"color": 7,
"width": 336,
"height": 528,
"content": "## Webhook Trigger node\nThis is your \u201cform endpoint\u201d. Your website (or Postman) will send POST JSON here.\n\n\n\n\n\n\n\n\n\n\n\n\nSend a POST request to the webhook URL with JSON body:\n\n```json\n{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"company\": \"johnscompany\",\n \"message\": \"Need help...\",\n \"source\": \"contact_page\",\n \"enrich\": false,\n \"destination\": \"sheets\"\n}\n"
},
"typeVersion": 1
},
{
"id": "4af97ba0-be13-4b29-a275-367da6fe5895",
"name": "Normalize leads",
"type": "n8n-nodes-base.set",
"position": [
-1248,
576
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "fdafa238-d50d-4b8f-993d-a89173abcad6",
"name": "lead.email",
"type": "string",
"value": "={{($json.email?.toLowerCase().trim()) || ($json.body[0].email?.trim())}}"
},
{
"id": "e701fe0b-2b52-4002-874f-72ef91bdc2f3",
"name": "lead.firstName",
"type": "string",
"value": "={{$json.firstName?.trim() || $json.body[0].firstName.trim()}}"
},
{
"id": "ba254dae-3cf6-4c8b-b453-4129190f1d38",
"name": "lead.lastName",
"type": "string",
"value": "={{$json.lastName?.trim() || $json.body[0].lastName.trim()}}"
},
{
"id": "759d2055-3464-42ba-a40c-8b8b1e32522b",
"name": "lead.name",
"type": "string",
"value": "={{ ($json.body[0].firstName) + ' ' + ($json.body[0].lastName)\n|| ($json.body.firstName) + ' ' + ($json.body.lastName) \n|| ($json.firstName) + ' ' + ($json.lastName)}}\n"
},
{
"id": "ade16815-f24f-421f-935a-72054353c124",
"name": "lead.company",
"type": "string",
"value": "={{$json.company?.trim() || $json.body[0].company?.trim()}}"
},
{
"id": "146f39b2-ef44-437c-aa73-1e91db1903c6",
"name": "lead.message",
"type": "string",
"value": "={{($json.message?.trim()) || ($json.body[0].message?.trim())}}"
},
{
"id": "818ff320-014f-441b-b4b6-14cd36eab141",
"name": "lead.source",
"type": "string",
"value": "={{$json.source || $json.body[0].source || 'unknown'}}"
},
{
"id": "ed6c5892-2c83-4e10-aced-c797b4824423",
"name": "lead.destination",
"type": "string",
"value": "={{$json.destination || $json.body[0].destination?.trim() || 'sheets'}}"
},
{
"id": "cb32a8c5-1a7c-4fab-850e-abcf0f2639ac",
"name": "lead.receivedAt",
"type": "string",
"value": "={{$now}}"
},
{
"id": "20bb589c-f01d-4bdb-bf40-2de87b1db963",
"name": "lead.enrich",
"type": "boolean",
"value": "={{ $json.enrich || $json.body[0].enrich}}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "00bfb2a3-fb48-497b-8fdd-cbaf2c190c5c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
480
],
"parameters": {
"color": 7,
"width": 640,
"height": 256,
"content": "## Normalize And Validate Lead\nReal data is messy. Trim spaces, lowercase email, create a consistent object once.\n\n\n\n\n\n\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "b908d026-0f74-4449-8d1a-d8440a045efe",
"name": "Code - Validate Lead",
"type": "n8n-nodes-base.code",
"position": [
-1040,
576
],
"parameters": {
"jsCode": "const lead = $json.lead || {};\n\nconst missingFields = [];\nif (!lead.email) missingFields.push(\"email\");\nif (!lead.message) missingFields.push(\"message\");\n\nif (!lead.firstName && !lead.lastName && !lead.company) {\n missingFields.push(\"firstName/lastName/company (at least one)\");\n}\n\n// Basic spam checks (optional, but useful)\nconst isLikelySpam =\n typeof lead.message === \"string\" &&\n lead.message.length < 5; // too short, suspicious\n\nconst isValid = missingFields.length === 0 && !isLikelySpam;\n\nreturn [\n {\n json: {\n lead,\n isValid,\n missingFields,\n isLikelySpam,\n errorMessage: !isValid\n ? (missingFields.length > 0\n ? `Missing required fields: ${missingFields.join(\", \")}`\n : \"Message looks too short / suspicious.\")\n : null,\n },\n },\n];\n"
},
"typeVersion": 2
},
{
"id": "677ccaee-9604-4267-b622-3ea26b566434",
"name": "IF node \u2014 Is Valid?",
"type": "n8n-nodes-base.if",
"position": [
-816,
576
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7119a1cf-066b-4c44-a97f-41a72f531d85",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{$json.isValid}}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "0ddc6d37-18a3-4899-b4f6-54cafd1861ad",
"name": "Respond to Webhook \u2014 400 Bad Request (invalid path)",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-528,
688
],
"parameters": {
"options": {
"responseCode": 400
},
"respondWith": "json",
"responseBody": "={\n \"ok\": false,\n \"error\": \"{{$json.errorMessage}}\",\n \"missingFields\": \"{{$json.missingFields}}\"\n}\n"
},
"typeVersion": 1.5
},
{
"id": "7f6197a0-365e-4425-930d-6e8a8ce5e9af",
"name": "IF node \u2014 Enrichment enabled?",
"type": "n8n-nodes-base.if",
"position": [
-528,
448
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "4755a7b1-b4cd-45a4-9e67-4e326818404d",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.lead.enrich }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "451bec77-001a-44ca-ade7-400b2bcffad1",
"name": "HTTP Request - Enrich (Optional)",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
-256,
432
],
"parameters": {
"url": "https://example.com",
"method": "POST",
"options": {
"timeout": 20000
}
},
"typeVersion": 4.3,
"alwaysOutputData": true
},
{
"id": "8bd7d0f0-d09e-461a-88e9-3ebe7bca9bdc",
"name": "Set - Merge Enrichment into lead",
"type": "n8n-nodes-base.set",
"position": [
304,
464
],
"parameters": {
"include": "except",
"options": {},
"assignments": {
"assignments": [
{
"id": "0ad79814-a5dc-4aa9-a479-08fb75096372",
"name": "=lead.enrichment",
"type": "string",
"value": "={{ $json.data || null}}"
}
]
},
"excludeFields": "data",
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "597e8af4-d5c1-4947-af3d-1b2480986995",
"name": "Switch - Choose destination (Sheets vs HubSpot)",
"type": "n8n-nodes-base.switch",
"position": [
576,
464
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Google Sheets",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "85fcd379-6b3c-477b-8cc1-8f5517305c9e",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{($json.lead.destination || 'sheets').toLowerCase()}}",
"rightValue": "sheets"
}
]
},
"renameOutput": true
},
{
"outputKey": "HubSpot",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ffb2d932-ab47-4a0f-8b7b-25e011abd92b",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{($json.lead.destination || 'sheets').toLowerCase()}}",
"rightValue": "hubspot"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.4
},
{
"id": "5767a86b-99db-4fd5-bf40-5d4c116f87d4",
"name": "Google Sheets- Lookup by email",
"type": "n8n-nodes-base.googleSheets",
"onError": "continueRegularOutput",
"position": [
896,
352
],
"parameters": {
"columns": {
"value": {
"name": "={{$json.lead.name}}",
"email": "={{$json.lead.email}}",
"source": "={{$json.lead.source}}",
"status": "New",
"company": "={{$json.lead.company}}",
"message": "={{$json.lead.message}}",
"updatedAt": "={{$now}}",
"enrichment": "={{JSON.stringify($json.lead.enrichment || null)}}",
"receivedAt": "={{$json.lead.receivedAt || $now}}",
"destination": "={{$json.lead.destination}}"
},
"schema": [
{
"id": "email",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "name",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "email",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "company",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "message",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "source",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "source",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "destination",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "destination",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "enrichment",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "enrichment",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "updatedAt",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "updatedAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "receivedAt",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "receivedAt",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"email"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.7
},
{
"id": "dda6f43a-ecf0-4664-acfc-3db36362d17f",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
-112
],
"parameters": {
"color": 7,
"width": 608,
"height": 288,
"content": "## Example sheet pattern\n```\nreceivedAt \u2192 ={{$json.lead.receivedAt || $now}}\nname \u2192 ={{$json.lead.name}}\nemail \u2192 ={{$json.lead.email}}\ncompany \u2192 ={{$json.lead.company}}\nmessage \u2192 ={{$json.lead.message}}\nsource \u2192 ={{$json.lead.source}}\ndestination \u2192 ={{$json.lead.destination}}\nenrichment \u2192 ={{JSON.stringify($json.lead.enrichment || null)}}\nupdatedAt \u2192 ={{$now}}\nstatus \u2192 New\n```"
},
"typeVersion": 1
},
{
"id": "01f3f746-e727-44be-b178-c520fcb214ed",
"name": "Set - Snapshot data",
"type": "n8n-nodes-base.set",
"position": [
-256,
272
],
"parameters": {
"options": {},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "dd12a7d4-a64c-40a1-bd50-bcc40327fc0b",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
-48,
288
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "682bd14c-2020-4641-b4b4-e5b5ccc01333",
"name": "IF - Sheets update Failed?",
"type": "n8n-nodes-base.if",
"position": [
1088,
352
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "072eb98f-acf5-4424-8cf4-33c433f7e546",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{$json.error?.message}}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "c9cfeab2-aed5-4f36-b061-5e2a78798517",
"name": "Slack - update failed",
"type": "n8n-nodes-base.slack",
"position": [
1312,
224
],
"parameters": {
"text": "=={{ `\u274c Google Sheets update failed for a new row \nerror details: ${$json.error || $json.errorMessage || \"-\"}\n\nlead data:\nName: ${$json.name ?? \"-\"}\nEmail: ${$json.email ?? \"-\"}\nCompany: ${$json.company ?? \"-\"}\nStatus: ${$json.status ?? \"-\"}\nDestination: ${$json.destination ?? \"-\"}\nReceived: ${$json.receivedAt ?? \"-\"}\nUpdated: ${$json.updatedAt ?? \"-\"}\nMessage:${$json.message ?? \"-\"}\n\nEnrichment (first 300 chars):\n${($json.enrichment ?? \"\").toString().slice(0, 300)}${(($json.enrichment ?? \"\").toString().length > 300 ? \"...\" : \"\")}` }}",
"user": {
"__rl": true,
"mode": "list",
"value": "U0A672GG97E",
"cachedResultName": "defitetra"
},
"select": "user",
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.4
},
{
"id": "875946be-f203-4863-b5a6-6cd53d29432a",
"name": "Slack - Successfully updated",
"type": "n8n-nodes-base.slack",
"position": [
1312,
368
],
"parameters": {
"text": "=={{ `\u2705 Google Sheets updated successfully with a new lead. \nLead details:\n\nName: ${$json.name ?? \"-\"}\nEmail: ${$json.email ?? \"-\"}\nCompany: ${$json.company ?? \"-\"}\nStatus: ${$json.status ?? \"-\"}\nDestination: ${$json.destination ?? \"-\"}\nReceived: ${$json.receivedAt ?? \"-\"}\nUpdated: ${$json.updatedAt ?? \"-\"}\nMessage:${$json.message ?? \"-\"}\n\nEnrichment (first 300 chars):\n${($json.enrichment ?? \"\").toString().slice(0, 300)}${(($json.enrichment ?? \"\").toString().length > 300 ? \"...\" : \"\")}` }}",
"user": {
"__rl": true,
"mode": "list",
"value": "U0A672GG97E",
"cachedResultName": "defitetra"
},
"select": "user",
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.4
},
{
"id": "75b7dd2d-82a4-43b3-a919-8e9f87e1c974",
"name": "Hubspot - Create/Update contacts",
"type": "n8n-nodes-base.hubspot",
"onError": "continueRegularOutput",
"position": [
896,
560
],
"parameters": {
"email": "={{ $json.lead.email }}",
"options": {},
"additionalFields": {}
},
"typeVersion": 2.2
},
{
"id": "dc890918-1c7a-40c1-91cc-3d6264df6b26",
"name": "IF - HubSpot Failed?",
"type": "n8n-nodes-base.if",
"position": [
1088,
560
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "072eb98f-acf5-4424-8cf4-33c433f7e546",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{$json.error?.message || $json.error}}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "4f1b0f45-4f50-4950-8b21-7d2a3f15ad3b",
"name": "Slack - HubSpot Successfully updated",
"type": "n8n-nodes-base.slack",
"position": [
1312,
688
],
"parameters": {
"text": "=={{ `\u2705 HubSpot updated successfully with a new lead. \nLead details:\n\nName: ${$json.name ?? \"-\"}\nEmail: ${$json.email ?? \"-\"}\nCompany: ${$json.company ?? \"-\"}\nStatus: ${$json.status ?? \"-\"}\nDestination: ${$json.destination ?? \"-\"}\nReceived: ${$json.receivedAt ?? \"-\"}\nUpdated: ${$json.updatedAt ?? \"-\"}\nMessage:${$json.message ?? \"-\"}\n\nEnrichment (first 300 chars):\n${($json.enrichment ?? \"\").toString().slice(0, 300)}${(($json.enrichment ?? \"\").toString().length > 300 ? \"...\" : \"\")}` }}",
"user": {
"__rl": true,
"mode": "list",
"value": "U0A672GG97E",
"cachedResultName": "defitetra"
},
"select": "user",
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.4
},
{
"id": "37eaee93-074a-4f8d-81e3-1a85549b0f3a",
"name": "Slack - HubSpot update failed",
"type": "n8n-nodes-base.slack",
"position": [
1312,
544
],
"parameters": {
"text": "=\u274c HubSpot write failed\nEmail: {{$json.lead.email}}\nError: {{$json.error.message}}\n",
"user": {
"__rl": true,
"mode": "list",
"value": "U0A672GG97E",
"cachedResultName": "defitetra"
},
"select": "user",
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.4
},
{
"id": "b2372856-de49-4a6d-abc2-7ab93053b4d0",
"name": "Respond to Webhook - 500 - Sheets",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1504,
224
],
"parameters": {
"options": {
"responseCode": 500
},
"respondWith": "json",
"responseBody": "{\n \"ok\": false,\n \"destination\": \"sheets\",\n \"error\": \"sheets write failed\"\n}\n"
},
"typeVersion": 1.5
},
{
"id": "efec202d-2400-4f00-bef2-5c8ba396e77e",
"name": "Respond to Webhook - 500 - HubSpot",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1504,
544
],
"parameters": {
"options": {
"responseCode": 500
},
"respondWith": "json",
"responseBody": "{\n \"ok\": false,\n \"destination\": \"hubspot\",\n \"error\": \"HubSpot write failed\"\n}\n"
},
"typeVersion": 1.5
},
{
"id": "dcdad04e-3a8b-413d-855b-0ff5fd701328",
"name": "Respond to Webhook - 200 - Sheets",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1504,
368
],
"parameters": {
"options": {
"responseCode": 200
},
"respondWith": "json",
"responseBody": "{\n \"ok\": true,\n \"destination\": \"sheets\",\n \"message\": \"sheets write successful\"\n}\n"
},
"typeVersion": 1.5
},
{
"id": "1fc24732-4de6-4381-aa4b-c72dbe450727",
"name": "Respond to Webhook - 200 - HubSpot",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1504,
688
],
"parameters": {
"options": {
"responseCode": 200
},
"respondWith": "json",
"responseBody": "{\n \"ok\": true,\n \"destination\": \"hubspot\",\n \"message\": \"hubspot write successful\"\n}\n"
},
"typeVersion": 1.5
},
{
"id": "39016202-1bed-4448-96fa-f7e1548c9a85",
"name": "Parse Webhook body",
"type": "n8n-nodes-base.code",
"position": [
-1456,
576
],
"parameters": {
"jsCode": "// If body arrives as a JSON string (text/plain), parse it. - To make sure that the next nodes received normalized data object\nconst b = $json.body;\n\nif (typeof b === \"string\") {\n try {\n const parsed = JSON.parse(b);\n return [{ json: { ...$json, ...parsed } }];\n } catch (e) {\n return [{ json: { ...$json, parseError: \"Invalid JSON in body\", rawBody: b } }];\n }\n}\n\nreturn [{ json: $json }];\n"
},
"typeVersion": 2
},
{
"id": "44cbf381-bd5e-40a0-a8fd-650ad1799f6f",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-592,
144
],
"parameters": {
"color": 7,
"width": 784,
"height": 496,
"content": "## Enrichment check\nCheck if enrichment is enabled, if yes, then the enrichment is done through third party service like Clearbit/Hunter. And then merged along the snapshot data.\nIn the HTTP Node, change ```url: https://example.com/``` to enrichment provider url"
},
"typeVersion": 1
},
{
"id": "c9ee2a87-5a44-4907-b111-f502598210b8",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
224,
368
],
"parameters": {
"color": 7,
"width": 256,
"height": 272,
"content": "Merge enrichment data along with the lead data, because HTTP node, looses the Input lead data. "
},
"typeVersion": 1
},
{
"id": "dd4dc7b0-0a49-4354-9f1a-c4baf123fc24",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
496,
368
],
"parameters": {
"color": 7,
"width": 304,
"height": 272,
"content": "Switch cases, based on destination where to save the lead data, ```Google Sheets``` OR ```HubSpot```"
},
"typeVersion": 1
},
{
"id": "115e38cd-8b50-4337-abc3-f985de3c3969",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2688,
-48
],
"parameters": {
"color": 7,
"width": 784,
"height": 1056,
"content": "## What this workflow does\n\n1. **Receives a lead** via **Webhook (POST)** (from your website form, Postman, etc.)\n2. **Parses the body** (handles both JSON and `text/plain` JSON strings)\n3. **Normalizes** fields (trim, lowercase email, defaults)\n4. **Validates** required fields and spam-like input\n - Invalid \u2192 returns **400** and stops\n5. **Optional enrichment**\n - If enabled \u2192 calls an HTTP enrichment endpoint (placeholder by default)\n - Merges enrichment back into the lead (because HTTP nodes don't preserve input automatically)\n6. **Routes lead** based on `destination`:\n - `sheets` \u2192 Upsert (append or update) in Google Sheets\n - `hubspot` \u2192 Upsert (create or update) contact in HubSpot\n7. **Slack alerts**\n - Success alert on update\n - Failure alert on update failure\n8. **Responds to webhook**\n - `200 OK` on success\n - `500` on storage failure (Sheets/HubSpot)\n\n---\n\n## Workflow diagram (high level)\n\n- **Webhook Trigger**\n- Parse Webhook Body\n- Normalize Leads\n- Validate Lead\n- IF Is Valid?\n - \u274c false \u2192 Respond 400\n - \u2705 true \u2192 IF Enrichment enabled?\n - \u2705 true \u2192 Snapshot + HTTP Enrich \u2192 Merge \u2192 Merge Enrichment into lead\n - \u274c false \u2192 (continues without enrichment)\n- Switch: destination (`sheets` vs `hubspot`)\n - Sheets branch:\n - Google Sheets Upsert by email\n - IF Sheets update failed?\n - \u274c Slack failure + Respond 500\n - \u2705 Slack success + Respond 200\n - HubSpot branch:\n - HubSpot Create/Update contact\n - IF HubSpot failed?\n - \u274c Slack failure + Respond 500\n - \u2705 Slack success + Respond 200\n\n"
},
"typeVersion": 1
}
],
"connections": {
"Merge": {
"main": [
[
{
"node": "Set - Merge Enrichment into lead",
"type": "main",
"index": 0
}
]
]
},
"Normalize leads": {
"main": [
[
{
"node": "Code - Validate Lead",
"type": "main",
"index": 0
}
]
]
},
"Webhook Trigger": {
"main": [
[
{
"node": "Parse Webhook body",
"type": "main",
"index": 0
}
]
]
},
"Parse Webhook body": {
"main": [
[
{
"node": "Normalize leads",
"type": "main",
"index": 0
}
]
]
},
"Set - Snapshot data": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Code - Validate Lead": {
"main": [
[
{
"node": "IF node \u2014 Is Valid?",
"type": "main",
"index": 0
}
]
]
},
"IF - HubSpot Failed?": {
"main": [
[
{
"node": "Slack - HubSpot update failed",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack - HubSpot Successfully updated",
"type": "main",
"index": 0
}
]
]
},
"IF node \u2014 Is Valid?": {
"main": [
[
{
"node": "IF node \u2014 Enrichment enabled?",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond to Webhook \u2014 400 Bad Request (invalid path)",
"type": "main",
"index": 0
}
]
]
},
"Slack - update failed": {
"main": [
[
{
"node": "Respond to Webhook - 500 - Sheets",
"type": "main",
"index": 0
}
]
]
},
"IF - Sheets update Failed?": {
"main": [
[
{
"node": "Slack - update failed",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack - Successfully updated",
"type": "main",
"index": 0
}
]
]
},
"Slack - Successfully updated": {
"main": [
[
{
"node": "Respond to Webhook - 200 - Sheets",
"type": "main",
"index": 0
}
]
]
},
"Slack - HubSpot update failed": {
"main": [
[
{
"node": "Respond to Webhook - 500 - HubSpot",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets- Lookup by email": {
"main": [
[
{
"node": "IF - Sheets update Failed?",
"type": "main",
"index": 0
}
]
]
},
"IF node \u2014 Enrichment enabled?": {
"main": [
[
{
"node": "Set - Snapshot data",
"type": "main",
"index": 0
},
{
"node": "HTTP Request - Enrich (Optional)",
"type": "main",
"index": 0
}
],
[
{
"node": "Set - Merge Enrichment into lead",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request - Enrich (Optional)": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Hubspot - Create/Update contacts": {
"main": [
[
{
"node": "IF - HubSpot Failed?",
"type": "main",
"index": 0
}
]
]
},
"Set - Merge Enrichment into lead": {
"main": [
[
{
"node": "Switch - Choose destination (Sheets vs HubSpot)",
"type": "main",
"index": 0
}
]
]
},
"Slack - HubSpot Successfully updated": {
"main": [
[
{
"node": "Respond to Webhook - 200 - HubSpot",
"type": "main",
"index": 0
}
]
]
},
"Switch - Choose destination (Sheets vs HubSpot)": {
"main": [
[
{
"node": "Google Sheets- Lookup by email",
"type": "main",
"index": 0
}
],
[
{
"node": "Hubspot - Create/Update contacts",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This is ideal for agencies, freelancers, SaaS founders, and small sales teams who want every lead recorded and followed up automatically within seconds. The workflow supports two storage options: HubSpot or Google Sheets (choose one branch). Enrichment (Clearbit/Hunter) is…
Source: https://n8n.io/workflows/12374/ — 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.
Real-Time Lead Processing - Captures and processes leads instantly from website forms with zero delay Intelligent Fit Scoring - Automatically scores leads 0-100 based on company size, seniority, and r
Monitor CRM accounts for hiring spikes by enriching HubSpot companies with PredictLeads job data and alerting your team via Slack.
A webhook receives a form submission with an email address The email is validated, then Lusha enriches the contact If phone or email is missing, a fallback provider fills the gaps via HTTP request Dat
SMB sales teams and SaaS companies who want to automatically prioritize and nurture new leads without manual qualification. Perfect for businesses getting 50+ leads per month who need to identify high
Automate your lead qualification pipeline — capture Typeform Webhook leads, enrich with APIs, score intelligently, and route to HubSpot, Slack, and Sheets in real-time.