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 →
{
"name": "Lead Intake Normalizer & Deduplicator",
"active": false,
"settings": {
"executionOrder": "v1",
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all",
"saveManualExecutions": true
},
"nodes": [
{
"name": "Lead Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-1120,
300
],
"parameters": {
"httpMethod": "POST",
"path": "lead-intake",
"responseMode": "responseNode",
"options": {}
}
},
{
"name": "Normalize & Validate Lead",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-900,
300
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/01-normalize-lead.js"
}
}
},
{
"name": "Lead Usable?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-680,
300
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "lead-usable",
"leftValue": "={{ $json.isUsable }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "Prepare CRM Lookup",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-460,
180
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/02-prepare-crm-lookup.js"
}
}
},
{
"name": "Use Live CRM?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-240,
180
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "live-crm",
"leftValue": "={{ $json.mockMode !== true && $json.canSearch === true }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "CRM: Find Contact",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-20,
60
],
"alwaysOutputData": false,
"parameters": {
"method": "GET",
"url": "https://services.leadconnectorhq.com/contacts/",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendQuery": true,
"specifyQuery": "keypair",
"queryParameters": {
"parameters": [
{
"name": "locationId",
"value": "={{ $('Prepare CRM Lookup').item.json.locationId }}"
},
{
"name": "query",
"value": "={{ $('Prepare CRM Lookup').item.json.query.phone || $('Prepare CRM Lookup').item.json.query.email }}"
},
{
"name": "limit",
"value": "20"
}
]
},
"sendHeaders": true,
"specifyHeaders": "keypair",
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
},
{
"name": "Accept",
"value": "application/json"
}
]
},
"options": {
"timeout": 15000,
"response": {
"response": {
"fullResponse": true,
"neverError": true
}
}
}
}
},
{
"name": "Classify Search Error",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
200,
-120
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/04-classify-api-error.js"
}
}
},
{
"name": "Retry Search?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
420,
-120
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "should-retry",
"leftValue": "={{ $json.shouldRetry }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "Backoff Wait",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
420,
-320
],
"parameters": {
"amount": "={{ $json.retry.waitSeconds }}",
"unit": "seconds"
}
},
{
"name": "Decide Create or Update",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
440,
180
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/03-decide-create-or-update.js"
}
}
},
{
"name": "Needs a CRM Write?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
660,
180
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "needs-write",
"leftValue": "={{ $json.action !== 'skip' && $json.mockMode !== true }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "Create or Update?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
880,
80
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "is-create",
"leftValue": "={{ $json.action === 'create' }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "CRM: Create Contact",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1120,
-60
],
"parameters": {
"method": "POST",
"url": "https://services.leadconnectorhq.com/contacts/",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"specifyHeaders": "keypair",
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
},
{
"name": "Accept",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify(Object.assign({}, $json.payload, { locationId: $json.locationId })) }}",
"options": {
"timeout": 15000,
"response": {
"response": {
"fullResponse": true,
"neverError": true
}
}
}
}
},
{
"name": "CRM: Update Contact",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1120,
240
],
"parameters": {
"method": "PUT",
"url": "=https://services.leadconnectorhq.com/contacts/{{ $json.contactId }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"specifyHeaders": "keypair",
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
},
{
"name": "Accept",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json.payload) }}",
"options": {
"timeout": 15000,
"response": {
"response": {
"fullResponse": true,
"neverError": true
}
}
}
}
},
{
"name": "Classify Write Error",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1360,
-80
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/04-classify-api-error.js"
}
}
},
{
"name": "Resolve Without Writing",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
880,
380
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/05-resolve-without-writing.js"
}
}
},
{
"name": "Build Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1600,
260
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/06-build-response.js"
}
}
},
{
"name": "Respond to Source",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1820,
260
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {
"responseCode": "={{ $json.statusCode }}"
}
}
},
{
"name": "README",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1140,
-80
],
"parameters": {
"width": 460,
"height": 300,
"color": 4,
"content": "## Lead Intake Normalizer\n\nPOST any lead payload to this webhook \u2014 website form, Facebook Lead Ads, Typeform, or a partner's own envelope \u2014 and it comes out as one clean contact or gets quarantined with a reason code.\n\n**Runs with no credentials.** `MOCK_MODE = true` in **Prepare CRM Lookup** makes the workflow answer its own CRM search from fixtures. Execute the workflow, POST the sample payload, and watch it work.\n\nTo go live: set `MOCK_MODE = false`, set `LOCATION_ID`, and attach a Header Auth credential to the three CRM nodes."
}
},
{
"name": "Error handling note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
180,
-420
],
"parameters": {
"width": 460,
"height": 240,
"color": 3,
"content": "## Why the retry loop is hand-built\n\nn8n's node-level **Retry On Fail** is ignored when **On Error** is set to a Continue option, so you cannot have both built-in retries and a graceful error branch.\n\nThese HTTP nodes use **Full Response + Never Error** instead of the error output, so a 429 arrives as a normal item with its `statusCode` and `headers` intact \u2014 which is the only way to read `Retry-After` at all. An IF routes non-2xx to the classifier.\n\nOnly the **search** retries. A failed create/update is never blindly repeated \u2014 a retried POST is how you get duplicate contacts."
}
},
{
"name": "Search Succeeded?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
200,
60
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "search-succeeded",
"leftValue": "={{ $json.statusCode >= 200 && $json.statusCode < 300 }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "Write Succeeded?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1120,
80
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "write-succeeded",
"leftValue": "={{ $json.statusCode >= 200 && $json.statusCode < 300 }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
}
],
"connections": {
"Lead Webhook": {
"main": [
[
{
"node": "Normalize & Validate Lead",
"type": "main",
"index": 0
}
]
]
},
"Normalize & Validate Lead": {
"main": [
[
{
"node": "Lead Usable?",
"type": "main",
"index": 0
}
]
]
},
"Lead Usable?": {
"main": [
[
{
"node": "Prepare CRM Lookup",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Response",
"type": "main",
"index": 0
}
]
]
},
"Prepare CRM Lookup": {
"main": [
[
{
"node": "Use Live CRM?",
"type": "main",
"index": 0
}
]
]
},
"Use Live CRM?": {
"main": [
[
{
"node": "CRM: Find Contact",
"type": "main",
"index": 0
}
],
[
{
"node": "Decide Create or Update",
"type": "main",
"index": 0
}
]
]
},
"CRM: Find Contact": {
"main": [
[
{
"node": "Search Succeeded?",
"type": "main",
"index": 0
}
]
]
},
"Classify Search Error": {
"main": [
[
{
"node": "Retry Search?",
"type": "main",
"index": 0
}
]
]
},
"Retry Search?": {
"main": [
[
{
"node": "Backoff Wait",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Response",
"type": "main",
"index": 0
}
]
]
},
"Backoff Wait": {
"main": [
[
{
"node": "CRM: Find Contact",
"type": "main",
"index": 0
}
]
]
},
"Decide Create or Update": {
"main": [
[
{
"node": "Needs a CRM Write?",
"type": "main",
"index": 0
}
]
]
},
"Needs a CRM Write?": {
"main": [
[
{
"node": "Create or Update?",
"type": "main",
"index": 0
}
],
[
{
"node": "Resolve Without Writing",
"type": "main",
"index": 0
}
]
]
},
"Create or Update?": {
"main": [
[
{
"node": "CRM: Create Contact",
"type": "main",
"index": 0
}
],
[
{
"node": "CRM: Update Contact",
"type": "main",
"index": 0
}
]
]
},
"CRM: Create Contact": {
"main": [
[
{
"node": "Write Succeeded?",
"type": "main",
"index": 0
}
]
]
},
"CRM: Update Contact": {
"main": [
[
{
"node": "Write Succeeded?",
"type": "main",
"index": 0
}
]
]
},
"Classify Write Error": {
"main": [
[
{
"node": "Build Response",
"type": "main",
"index": 0
}
]
]
},
"Resolve Without Writing": {
"main": [
[
{
"node": "Build Response",
"type": "main",
"index": 0
}
]
]
},
"Build Response": {
"main": [
[
{
"node": "Respond to Source",
"type": "main",
"index": 0
}
]
]
},
"Search Succeeded?": {
"main": [
[
{
"node": "Decide Create or Update",
"type": "main",
"index": 0
}
],
[
{
"node": "Classify Search Error",
"type": "main",
"index": 0
}
]
]
},
"Write Succeeded?": {
"main": [
[
{
"node": "Build Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Classify Write Error",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Lead Intake Normalizer & Deduplicator. Uses httpRequest. Webhook trigger; 22 nodes.
Source: https://github.com/ihthicodes/n8n-agency-demos/blob/main/demos/01-lead-intake-normalizer/workflow.src.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 workflow automates bulk email campaigns with built-in validation, deliverability protection, and smart send-time optimization.
Universal Lead Processing Workflow. Uses googleSheets, gmail, httpRequest, gmailTrigger. Webhook trigger; 34 nodes.
This workflow turns Feishu bot messages into a human-in-the-loop short-video production pipeline, using Tavily for web search, DeepSeek for topic and script generation, Jimeng AI for text-to-video, an
This workflow is designed to manage the assignment and validation of unique QR code coupons within a lead generation system with SuiteCRM.
This workflow acts as an instant SDR that replies to new inbound leads across multiple channels in real time. It first captures and normalizes all incoming lead data into a unified structure. The work