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": "CRM List Hygiene & Duplicate Merge Planner",
"active": false,
"settings": {
"executionOrder": "v1",
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all",
"saveManualExecutions": true
},
"nodes": [
{
"name": "Run Audit Now",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-1180,
260
],
"parameters": {}
},
{
"name": "Weekly Audit",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-1180,
440
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
1
],
"triggerAtHour": 6,
"triggerAtMinute": 0
}
]
}
}
},
{
"name": "Init Audit Run",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-960,
350
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/01-init-audit-run.js"
}
}
},
{
"name": "Build Page Request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-740,
350
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/02-build-page-request.js"
}
}
},
{
"name": "Use Live CRM?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-520,
350
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "use-live",
"leftValue": "={{ $json.useLive }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "CRM: Fetch Contact Page",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-280,
220
],
"parameters": {
"method": "GET",
"url": "https://services.leadconnectorhq.com/contacts/",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendQuery": true,
"specifyQuery": "keypair",
"queryParameters": {
"parameters": [
{
"name": "locationId",
"value": "={{ $('Build Page Request').item.json.request.locationId }}"
},
{
"name": "limit",
"value": "={{ $('Build Page Request').item.json.request.limit }}"
},
{
"name": "startAfterId",
"value": "={{ $('Build Page Request').item.json.request.cursor }}"
}
]
},
"sendHeaders": true,
"specifyHeaders": "keypair",
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
},
{
"name": "Accept",
"value": "application/json"
}
]
},
"options": {
"timeout": 30000,
"response": {
"response": {
"fullResponse": true,
"neverError": true
}
}
}
}
},
{
"name": "Mock Contact Page",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-280,
480
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/03-mock-contact-page.js"
}
}
},
{
"name": "Classify Fetch Error",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-40,
20
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/05-classify-api-error.js"
}
}
},
{
"name": "Retry Fetch?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
180,
20
],
"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": [
180,
-180
],
"parameters": {
"amount": "={{ $json.retry.waitSeconds }}",
"unit": "seconds"
}
},
{
"name": "Accumulate Page",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
200,
350
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/04-accumulate-page.js"
}
}
},
{
"name": "More Pages?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
400,
350
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "has-more",
"leftValue": "={{ $json.audit.hasMore }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "Cluster & Score Contacts",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
640,
460
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/06-cluster-and-score.js"
}
}
},
{
"name": "Build Reports",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
880,
580
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/07-build-reports.js"
}
}
},
{
"name": "Apply the Merges?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
880,
340
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "not-dry-run",
"leftValue": "={{ $json.config.dryRun !== true && $json.mergePlan.length > 0 }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "Dry Run \u2014 Nothing Written",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
1120,
440
],
"parameters": {}
},
{
"name": "Prepare Merge Batches",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
220
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/08-prepare-merge-batches.js"
}
}
},
{
"name": "CRM: Merge Contacts",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1340,
220
],
"parameters": {
"method": "POST",
"url": "https://services.leadconnectorhq.com/contacts/merge",
"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": 30000,
"batching": {
"batch": {
"batchSize": 5,
"batchInterval": 1000
}
},
"response": {
"response": {
"fullResponse": true,
"neverError": true
}
}
}
}
},
{
"name": "Record Merge Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1580,
220
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/09-record-merge-results.js"
}
}
},
{
"name": "README",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1200,
-80
],
"parameters": {
"width": 480,
"height": 300,
"color": 4,
"content": "## CRM List Hygiene Audit\n\nWalks the whole contact list, finds real duplicates, scores every record for deliverability, and produces a merge plan as a CSV.\n\n**Dry run by default.** `dryRun: true` in **Init Audit Run** means it writes nothing \u2014 it produces a plan for a human to approve. `mockMode: true` means it needs no credentials at all: hit **Test workflow** and it runs against 24 built-in fixture contacts.\n\nThe interesting node is **Cluster & Score Contacts**. Open it."
}
},
{
"name": "Pagination note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-760,
-260
],
"parameters": {
"width": 420,
"height": 220,
"color": 5,
"content": "## The loop\n\n**Build Page Request** is the loop head, and it exists because an HTTP node's error output does not carry the item that went in. Anything downstream of a retry has to be able to rebuild the loop state \u2014 this does it in one place.\n\nFour stop conditions: no cursor, empty page, page ceiling, contact ceiling. Plus a stalled-cursor check, which is how paginators actually fail in production."
}
},
{
"name": "Safety note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
900,
-80
],
"parameters": {
"width": 420,
"height": 240,
"color": 3,
"content": "## Before it writes anything\n\nThree gates between a plan and a destructive bulk operation:\n\n1. `dryRun` must be explicitly turned off\n2. confidence floor of 90 \u2014 anything below never gets issued\n3. a hard ceiling of 100 merges per run, throttled to 5 req/s\n\nPairs with conflicting evidence (different phones, different first names on a shared email) go to **needs-review.csv**, never to the merge plan. Merging two real customers is not reversible."
}
},
{
"name": "Page Fetched OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-40,
220
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "page-fetched-ok",
"leftValue": "={{ $json.statusCode >= 200 && $json.statusCode < 300 }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"name": "Report Partial Audit",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
400,
20
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": {
"$code": "code/10-report-partial-audit.js"
}
}
}
],
"connections": {
"Run Audit Now": {
"main": [
[
{
"node": "Init Audit Run",
"type": "main",
"index": 0
}
]
]
},
"Weekly Audit": {
"main": [
[
{
"node": "Init Audit Run",
"type": "main",
"index": 0
}
]
]
},
"Init Audit Run": {
"main": [
[
{
"node": "Build Page Request",
"type": "main",
"index": 0
}
]
]
},
"Build Page Request": {
"main": [
[
{
"node": "Use Live CRM?",
"type": "main",
"index": 0
}
]
]
},
"Use Live CRM?": {
"main": [
[
{
"node": "CRM: Fetch Contact Page",
"type": "main",
"index": 0
}
],
[
{
"node": "Mock Contact Page",
"type": "main",
"index": 0
}
]
]
},
"CRM: Fetch Contact Page": {
"main": [
[
{
"node": "Page Fetched OK?",
"type": "main",
"index": 0
}
]
]
},
"Mock Contact Page": {
"main": [
[
{
"node": "Accumulate Page",
"type": "main",
"index": 0
}
]
]
},
"Classify Fetch Error": {
"main": [
[
{
"node": "Retry Fetch?",
"type": "main",
"index": 0
}
]
]
},
"Retry Fetch?": {
"main": [
[
{
"node": "Backoff Wait",
"type": "main",
"index": 0
}
],
[
{
"node": "Report Partial Audit",
"type": "main",
"index": 0
}
]
]
},
"Backoff Wait": {
"main": [
[
{
"node": "Build Page Request",
"type": "main",
"index": 0
}
]
]
},
"Accumulate Page": {
"main": [
[
{
"node": "More Pages?",
"type": "main",
"index": 0
}
]
]
},
"More Pages?": {
"main": [
[
{
"node": "Build Page Request",
"type": "main",
"index": 0
}
],
[
{
"node": "Cluster & Score Contacts",
"type": "main",
"index": 0
}
]
]
},
"Cluster & Score Contacts": {
"main": [
[
{
"node": "Build Reports",
"type": "main",
"index": 0
},
{
"node": "Apply the Merges?",
"type": "main",
"index": 0
}
]
]
},
"Apply the Merges?": {
"main": [
[
{
"node": "Prepare Merge Batches",
"type": "main",
"index": 0
}
],
[
{
"node": "Dry Run \u2014 Nothing Written",
"type": "main",
"index": 0
}
]
]
},
"Prepare Merge Batches": {
"main": [
[
{
"node": "CRM: Merge Contacts",
"type": "main",
"index": 0
}
]
]
},
"CRM: Merge Contacts": {
"main": [
[
{
"node": "Record Merge Results",
"type": "main",
"index": 0
}
]
]
},
"Page Fetched OK?": {
"main": [
[
{
"node": "Accumulate Page",
"type": "main",
"index": 0
}
],
[
{
"node": "Classify Fetch Error",
"type": "main",
"index": 0
}
]
]
},
"Report Partial Audit": {
"main": [
[
{
"node": "Cluster & Score 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
CRM List Hygiene & Duplicate Merge Planner. Uses httpRequest. Event-driven trigger; 24 nodes.
Source: https://github.com/ihthicodes/n8n-agency-demos/blob/main/demos/02-crm-list-hygiene/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 listens for an “Approved” label on a Trello card, reads the AI draft bookkeeping JSON from card comments, and posts the corresponding transaction to Xero. It then adds a Xero deep link b
02_LLM_Pipeline v1.0. Uses executeWorkflowTrigger, httpRequest, seaTable. Event-driven trigger; 65 nodes.
This workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t
[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.
[](https://youtu.be/c7yCZhmMjtI)