This workflow corresponds to n8n.io template #12702 — we link there as the canonical source.
This workflow follows the Form Trigger → Google Sheets 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 →
{
"id": "<REDACTED_WORKFLOW_ID>",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Lead Routing Engine with SLA Auto-Reassignment",
"tags": [],
"nodes": [
{
"id": "081127cd-9a65-420f-bcc9-ec00edd129c0",
"name": "1) Form Trigger (New Lead)",
"type": "n8n-nodes-base.formTrigger",
"position": [
1008,
656
],
"parameters": {
"options": {
"buttonLabel": "Submit"
},
"formTitle": "Lead Intake (Generic Form Trigger)",
"formFields": {
"values": [
{
"fieldName": "Name",
"fieldLabel": "Name",
"requiredField": true
},
{
"fieldName": "Phone",
"fieldLabel": "Phone",
"placeholder": "0812xxx / +62812xxx / 62812xxx",
"requiredField": true
}
]
}
},
"typeVersion": 2.4
},
{
"id": "3379ea2e-316c-407e-adee-75094f1904cd",
"name": "2) Prep Lead",
"type": "n8n-nodes-base.code",
"position": [
1232,
656
],
"parameters": {
"jsCode": "// =============================================\n// 2) Prep Lead (Normalize + Lead ID + Funnel Stage)\n// =============================================\n// Funnel stages:\n// - NEW, CONTACTED, QUALIFIED, CLOSED_LOST\n//\n// This node:\n// - generates a safe lead_id\n// - normalizes phone into 62xxxx\n// - sets initial stage = NEW\n\nfunction normalizePhone(input) {\n let p = String(input || '').trim();\n p = p.replace(/\\s+/g, '');\n p = p.replace(/[^0-9+]/g, '');\n\n if (p.startsWith('+62')) p = '62' + p.slice(3);\n if (p.startsWith('0')) p = '62' + p.slice(1);\n if (p.startsWith('62')) return p;\n if (/^\\d+$/.test(p)) return '62' + p;\n return p;\n}\n\nfunction makeLeadId() {\n const ts = Date.now().toString(36);\n const rnd = Math.random().toString(36).slice(2, 8);\n return `L-${ts}-${rnd}`.toUpperCase();\n}\n\nconst now = new Date().toISOString();\n\nreturn [{\n json: {\n lead_id: makeLeadId(),\n name: String($json.Name || '').trim(),\n phone: normalizePhone($json.Phone),\n\n // Funnel stage\n stage: 'NEW',\n\n // Routing state\n assigned_sales: '',\n sales_slack_id: '',\n route_count: 0,\n\n // Timestamps\n created_at: now,\n initial_assigned_at: '',\n assigned_at: '',\n last_routed_at: ''\n }\n}];"
},
"typeVersion": 2
},
{
"id": "43e46f8a-bdf3-4617-a342-fbb756f3d986",
"name": "3) Get Sales List (active=ON)",
"type": "n8n-nodes-base.googleSheets",
"position": [
1456,
656
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "sales_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "4b206a78-d668-4750-b005-99baa882807f",
"name": "4) Build Sales List",
"type": "n8n-nodes-base.code",
"position": [
1680,
656
],
"parameters": {
"jsCode": "// =============================================\n// 4) Build Sales List (1 object)\n// =============================================\n// Output: sales_list[] is used for initial assignment and re-routing.\n\nconst salesList = items\n .map(i => i.json)\n .filter(r => {\n const a = String(r.active || '').toUpperCase();\n return a === 'ON' || a === 'TRUE' || r.active === true;\n })\n .map(r => ({\n name: String(r.name || '').trim(),\n email: String(r.email || '').trim(),\n slack_id: String(r.slack_id || '').trim(),\n active: true\n }))\n .filter(r => r.name && r.slack_id);\n\nif (!salesList.length) {\n throw new Error('No active sales found. Check sales sheet: name, slack_id, active=ON');\n}\n\nreturn [{ json: { sales_list: salesList, sales_count: salesList.length } }];"
},
"typeVersion": 2
},
{
"id": "aacd08b3-e26b-4545-b0b1-478c69785d0d",
"name": "5) Get routing_state (last_index)",
"type": "n8n-nodes-base.googleSheets",
"position": [
1904,
656
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "last_index",
"lookupColumn": "key"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "routing_state_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "63feae74-e4fa-45fa-85a8-d70cf8264add",
"name": "6) Initial Assign (Round Robin)",
"type": "n8n-nodes-base.code",
"position": [
2128,
656
],
"parameters": {
"jsCode": "// 6) Initial Assign (Round Robin)\n// Read the lead directly from node 2 (to avoid being overwritten by Google Sheets nodes)\n\nconst lead = $items('2) Prep Lead')[0].json; // \u2705 original lead\nconst sales = $items('4) Build Sales List')[0].json.sales_list;\n\n// routing_state is typically from node 5 (key/value)\nconst rs = $items('5) Get routing_state (last_index)')[0]?.json || {};\nconst lastIndex = Number(rs.value ?? -1);\n\nconst nextIndex = (lastIndex + 1) % sales.length;\nconst assigned = sales[nextIndex];\n\nconst now = new Date().toISOString();\n\nreturn [{\n json: {\n ...lead,\n\n assigned_sales: assigned.name,\n sales_slack_id: assigned.slack_id,\n\n assigned_sales_index: nextIndex,\n routing_key: 'last_index',\n\n route_count: Number(lead.route_count || 0) + 1,\n\n initial_assigned_at: lead.initial_assigned_at || now,\n assigned_at: now,\n last_routed_at: now,\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "84bd91ec-f4c6-4abb-bc2f-83ec9afffb97",
"name": "7) Update routing_state",
"type": "n8n-nodes-base.googleSheets",
"position": [
2352,
560
],
"parameters": {
"columns": {
"value": {
"key": "={{ $json.routing_key }}",
"value": "={{ $json.assigned_sales_index }}"
},
"schema": [
{
"id": "key",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "key",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "value",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "value",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"key"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "routing_state_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "e79612be-ccc5-4a59-8da9-fdea4036ae6b",
"name": "8) Upsert Lead (leads sheet)",
"type": "n8n-nodes-base.googleSheets",
"position": [
2352,
752
],
"parameters": {
"columns": {
"value": {
"name": "={{ $json.name }}",
"phone": "={{ $json.phone }}",
"stage": "={{ $json.stage }}",
"lead_id": "={{ $json.lead_id }}",
"created_at": "={{ $json.created_at }}",
"assigned_at": "={{ $json.assigned_at }}",
"route_count": "={{ $json.route_count }}",
"assigned_sales": "={{ $json.assigned_sales }}",
"last_routed_at": "={{ $json.last_routed_at }}",
"sales_slack_id": "={{ $json.sales_slack_id }}",
"initial_assigned_at": "={{ $json.initial_assigned_at }}"
},
"schema": [
{
"id": "lead_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "lead_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "name",
"type": "string",
"display": true,
"required": false,
"displayName": "name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "stage",
"type": "string",
"display": true,
"required": false,
"displayName": "stage",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "assigned_sales",
"type": "string",
"display": true,
"required": false,
"displayName": "assigned_sales",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "sales_slack_id",
"type": "string",
"display": true,
"required": false,
"displayName": "sales_slack_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "initial_assigned_at",
"type": "string",
"display": true,
"required": false,
"displayName": "initial_assigned_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "assigned_at",
"type": "string",
"display": true,
"required": false,
"displayName": "assigned_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_routed_at",
"type": "string",
"display": true,
"required": false,
"displayName": "last_routed_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "route_count",
"type": "string",
"display": true,
"required": false,
"displayName": "route_count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "created_at",
"type": "string",
"display": true,
"required": false,
"displayName": "created_at",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"lead_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "lead_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "75bd650d-7e5f-46d6-b422-cff871904be9",
"name": "SLA Trigger (every 1 hour)",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1008,
1184
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.3
},
{
"id": "df9abcc5-683b-4345-9a47-39b0ddeafae2",
"name": "SLA) Get Sales List",
"type": "n8n-nodes-base.googleSheets",
"position": [
1232,
1184
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "sales_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "1a849c9c-cac8-4d79-808a-747addc1650d",
"name": "SLA) Build Sales List",
"type": "n8n-nodes-base.code",
"position": [
1456,
1184
],
"parameters": {
"jsCode": "// Reuse the same builder logic for the SLA path\nconst salesList = items\n .map(i => i.json)\n .filter(r => {\n const a = String(r.active || '').toUpperCase();\n return a === 'ON' || a === 'TRUE' || r.active === true;\n })\n .map(r => ({\n name: String(r.name || '').trim(),\n email: String(r.email || '').trim(),\n slack_id: String(r.slack_id || '').trim(),\n active: true\n }))\n .filter(r => r.name && r.slack_id);\n\nif (!salesList.length) throw new Error('No active sales found');\n\nreturn [{ json: { sales_list: salesList, sales_count: salesList.length } }];"
},
"typeVersion": 2
},
{
"id": "5a9d1951-f3fe-457a-baf2-64d3016a9f6b",
"name": "SLA) Get Leads (stage=NEW)",
"type": "n8n-nodes-base.googleSheets",
"position": [
1680,
1184
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "NEW",
"lookupColumn": "stage"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "lead_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "848e4f52-21dc-4a4d-b57e-04449767c114",
"name": "SLA) If last route >= 1 hour",
"type": "n8n-nodes-base.if",
"position": [
1904,
1184
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d684dde0-8f7d-4ecf-a07b-c0557a9b1323",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ (Date.now() - new Date($json.last_routed_at || $json.initial_assigned_at || $json.assigned_at || $json.created_at).getTime()) >= 60 * 60 * 1000 }}"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "ef2e8f85-a46f-44af-86a3-60230e7f8d7b",
"name": "SLA) Re-route to next sales",
"type": "n8n-nodes-base.code",
"position": [
2128,
1184
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// SLA) Hourly Re-route (per item) - n8n 2.1.4 compatible\n\nconst lead = $json;\nconst sales = $items('SLA) Build Sales List')[0].json.sales_list;\n\nif (!Array.isArray(sales) || sales.length === 0) {\n throw new Error(\"Sales list is empty. Check output of node 'SLA) Build Sales List'.\");\n}\n\nconst currentSlack = String(lead.sales_slack_id || '').trim();\nconst currentIndex = sales.findIndex(s => String(s.slack_id) === currentSlack);\nconst safeIndex = currentIndex === -1 ? 0 : currentIndex;\n\nconst nextIndex = (safeIndex + 1) % sales.length;\nconst nextSales = sales[nextIndex];\n\nconst now = new Date().toISOString();\n\n// IMPORTANT: For 'Run Once for Each Item', return MUST be an object, not an array\nreturn {\n json: {\n ...lead,\n assigned_sales: nextSales.name,\n sales_slack_id: nextSales.slack_id,\n route_count: Number(lead.route_count || 0) + 1,\n assigned_at: now,\n last_routed_at: now,\n }\n};\n"
},
"typeVersion": 2
},
{
"id": "0c2f96ca-edd6-4b09-9e56-a29e3d85b005",
"name": "SLA) Update lead (after reroute)",
"type": "n8n-nodes-base.googleSheets",
"position": [
2576,
1280
],
"parameters": {
"columns": {
"value": {
"lead_id": "={{ $json.lead_id }}",
"assigned_at": "={{ $json.assigned_at }}",
"route_count": "={{ $json.route_count }}",
"assigned_sales": "={{ $json.assigned_sales }}",
"last_routed_at": "={{ $json.last_routed_at }}",
"sales_slack_id": "={{ $json.sales_slack_id }}"
},
"schema": [
{
"id": "lead_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "lead_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "name",
"type": "string",
"display": true,
"required": false,
"displayName": "name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "stage",
"type": "string",
"display": true,
"required": false,
"displayName": "stage",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "assigned_sales",
"type": "string",
"display": true,
"required": false,
"displayName": "assigned_sales",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "sales_slack_id",
"type": "string",
"display": true,
"required": false,
"displayName": "sales_slack_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "initial_assigned_at",
"type": "string",
"display": true,
"required": false,
"displayName": "initial_assigned_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "assigned_at",
"type": "string",
"display": true,
"required": false,
"displayName": "assigned_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_routed_at",
"type": "string",
"display": true,
"required": false,
"displayName": "last_routed_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "route_count",
"type": "string",
"display": true,
"required": false,
"displayName": "route_count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "created_at",
"type": "string",
"display": true,
"required": false,
"displayName": "created_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"lead_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "lead_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "f2b2d1f5-5c79-4c15-bccb-68576372352b",
"name": "STAGE) Webhook (/demo-stage-update)",
"type": "n8n-nodes-base.webhook",
"position": [
1024,
1696
],
"parameters": {
"path": "demo-stage-update",
"options": {},
"httpMethod": "POST",
"responseMode": "lastNode"
},
"typeVersion": 2
},
{
"id": "bcdaf046-1550-4a7c-9203-fb3d0efd445e",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
512
],
"parameters": {
"width": 1904,
"height": 432,
"content": "## Intake & Initial Assignment (Round Robin)"
},
"typeVersion": 1
},
{
"id": "47ba8bf6-9a88-4740-b055-444a71837399",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
992
],
"parameters": {
"width": 2992,
"height": 512,
"content": "## SLA Monitor & Hourly Re-route"
},
"typeVersion": 1
},
{
"id": "2fae9d8f-876c-4402-a2ca-5fac8150ad29",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
1552
],
"parameters": {
"width": 1488,
"height": 432,
"content": "## \"Contacted\" Stage Update API (Stop Routing)"
},
"typeVersion": 1
},
{
"id": "1d86227e-35f4-4433-9be2-657ed6c8bc0f",
"name": "STAGE) Update lead stage to CONTACTED",
"type": "n8n-nodes-base.googleSheets",
"position": [
2144,
1600
],
"parameters": {
"columns": {
"value": {
"stage": "={{ $json.stage }}",
"lead_id": "={{ $json.lead_id }}",
"contacted_at": "={{ $json.contacted_at }}"
},
"schema": [
{
"id": "lead_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "lead_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "name",
"type": "string",
"display": true,
"required": false,
"displayName": "name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "stage",
"type": "string",
"display": true,
"required": false,
"displayName": "stage",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "assigned_sales",
"type": "string",
"display": true,
"required": false,
"displayName": "assigned_sales",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "sales_slack_id",
"type": "string",
"display": true,
"required": false,
"displayName": "sales_slack_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "initial_assigned_at",
"type": "string",
"display": true,
"required": false,
"displayName": "initial_assigned_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "assigned_at",
"type": "string",
"display": true,
"required": false,
"displayName": "assigned_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_routed_at",
"type": "string",
"display": true,
"required": false,
"displayName": "last_routed_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "route_count",
"type": "string",
"display": true,
"required": false,
"displayName": "route_count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "created_at",
"type": "string",
"display": true,
"required": false,
"displayName": "created_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "contacted_at",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "contacted_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"lead_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "lead_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "ae3aa50c-f879-4687-a17e-28d8ef01fa0c",
"name": "SLA) Slack Notify (reroute) & Button",
"type": "n8n-nodes-base.slack",
"position": [
2800,
1280
],
"parameters": {
"text": "=",
"select": "channel",
"blocksUi": "={\n \"blocks\": [\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"\ud83d\udd01 *Lead Re-routed (SLA)*\\n\\n*Lead ID:* {{ $items(\\\"SLA) Re-route to next sales\\\")[$itemIndex].json.lead_id }}\\n*Now Assigned to:* <@{{ $items(\\\"SLA) Re-route to next sales\\\")[$itemIndex].json.sales_slack_id }}>\\n*Name:* {{ $items(\\\"SLA) Re-route to next sales\\\")[$itemIndex].json.name }}\\n*Phone:* {{ $items(\\\"SLA) Re-route to next sales\\\")[$itemIndex].json.phone }}\\n*Route count:* {{ $items(\\\"SLA) Re-route to next sales\\\")[$itemIndex].json.route_count }}\"\n }\n },\n {\n \"type\": \"actions\",\n \"elements\": [\n {\n \"type\": \"button\",\n \"style\": \"primary\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Mark as CONTACTED\"\n },\n \"value\": \"{{ $items(\\\"SLA) Re-route to next sales\\\")[$itemIndex].json.lead_id }}\",\n \"action_id\": \"mark_contacted\"\n }\n ]\n }\n ]\n}",
"channelId": {
"__rl": true,
"mode": "id",
"value": "<REDACTED_SLACK_CHANNEL_ID_1>"
},
"messageType": "block",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.4
},
{
"id": "50fce6b6-5460-4441-9322-6bf5ef4073cf",
"name": "9) Slack Notify (New Lead) & Button",
"type": "n8n-nodes-base.slack",
"position": [
2576,
752
],
"parameters": {
"text": "=",
"select": "channel",
"blocksUi": "={\n \"blocks\": [\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \":scream: *ALERT!!! NEW LEAD ASSIGNED*\\n\\n*Lead ID:* {{ $json.lead_id }}\\n*Assigned to:* <@{{ $json.sales_slack_id }}>\\n*Name:* {{ $json.name }}\\n*Phone:* {{ $json.phone }}\\n\\n:hourglass_flowing_sand: *Please claim within 1 hour*\"\n }\n },\n {\n \"type\": \"actions\",\n \"elements\": [\n {\n \"type\": \"button\",\n \"style\": \"primary\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Mark as CONTACTED\"\n },\n \"value\": \"{{ $json.lead_id }}\",\n \"action_id\": \"mark_contacted\"\n }\n ]\n }\n ]\n}",
"channelId": {
"__rl": true,
"mode": "id",
"value": "<REDACTED_SLACK_CHANNEL_ID_1>"
},
"messageType": "block",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.4
},
{
"id": "8b9bed18-38c6-462a-b5df-99a84718d4b7",
"name": "STAGE) Lookup lead by lead_id",
"type": "n8n-nodes-base.googleSheets",
"position": [
1472,
1696
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $json.lead_id }}",
"lookupColumn": "lead_id"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "lead_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "ffc50ca6-b358-40f9-a9f9-e3458c1e03e4",
"name": "STAGE) Prep Payload",
"type": "n8n-nodes-base.code",
"position": [
1248,
1696
],
"parameters": {
"jsCode": "// =============================================\n// STAGE) Prep Payload (Slack Interactive - Block Actions)\n// =============================================\n// Slack interactive payload arrives as x-www-form-urlencoded:\n// $json.body.payload is a STRING containing JSON\n\nconst payloadStr = $json?.body?.payload;\n\nif (!payloadStr) {\n throw new Error('Missing body.payload from Slack (expected x-www-form-urlencoded payload)');\n}\n\nlet slack;\ntry {\n slack = JSON.parse(payloadStr);\n} catch (e) {\n throw new Error('Invalid JSON in body.payload');\n}\n\n// Extract lead_id from button value\nconst lead_id = String(slack?.actions?.[0]?.value || '').trim();\n\n// The user who clicked\nconst actor_slack_id = String(slack?.user?.id || '').trim();\n\n// response_url for direct message feedback (optional but recommended)\nconst response_url = String(slack?.response_url || '').trim();\n\n// Target stage to set (this button is for CONTACTED)\nconst stage = 'CONTACTED';\n\nif (!lead_id) throw new Error('lead_id required (expected slack.actions[0].value)');\nif (!actor_slack_id) throw new Error('actor_slack_id missing (expected slack.user.id)');\n\nconst now = new Date().toISOString();\n\nreturn [{\n json: {\n lead_id,\n stage,\n contacted_at: now,\n contacted_by: actor_slack_id,\n actor_slack_id,\n response_url, // \u2705 needed for \"Not allowed\" feedback\n }\n}];"
},
"typeVersion": 2
},
{
"id": "42020b30-cae9-4e49-852f-ccc5c601742b",
"name": "STAGE) If authorized",
"type": "n8n-nodes-base.if",
"position": [
1920,
1696
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1ca8acf2-bd8b-4cfd-852e-f09d3e7260c7",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.authorized }}",
"rightValue": false
}
]
}
},
"typeVersion": 2.3
},
{
"id": "486062f2-bd86-4006-aeea-8ef2a6230101",
"name": "STAGE) Slack Feedback - Not allowed",
"type": "n8n-nodes-base.httpRequest",
"position": [
2144,
1792
],
"parameters": {
"url": "={{ $json.response_url }}",
"method": "POST",
"options": {},
"jsonBody": "={\n \"replace_original\": false,\n \"text\": \"\u26a0\ufe0f *Access denied.* This lead is currently handled by another sales representative.\\n\\nAssigned to: <@{{ $json.assigned_sales_slack_id }}>\\nClicked by: <@{{ $json.actor_slack_id }}>\\nLead ID: {{ $json.lead_id }}\"\n}\n",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.3
},
{
"id": "08a8fef6-9693-4791-b899-308816a89666",
"name": "STAGE) Authorize click (must be assigned sales)",
"type": "n8n-nodes-base.code",
"position": [
1696,
1696
],
"parameters": {
"jsCode": "// =============================================\n// STAGE) Authorization + Idempotency Gate\n// - Only the assigned sales can mark CONTACTED\n// - Only allowed if current stage === 'NEW'\n// =============================================\n\nconst payload = $items('STAGE) Prep Payload')[0].json;\n\n// Lookup result: item from node \"STAGE) Lookup lead by lead_id\"\nconst leadRow = items?.[0]?.json;\n\nif (!leadRow) {\n return [{\n json: {\n ...payload,\n authorized_owner: false,\n stage_allowed: false,\n authorized: false,\n reason: 'LEAD_NOT_FOUND',\n assigned_sales_slack_id: '',\n current_stage: '',\n lead_name: '',\n lead_phone: '',\n }\n }];\n}\n\nconst assigned = String(leadRow.sales_slack_id || '').trim();\nconst actor = String(payload.actor_slack_id || payload.contacted_by || '').trim();\nconst currentStage = String(leadRow.stage || '').trim().toUpperCase();\n\n// Gate #1: owner check\nconst authorizedOwner = assigned && actor && (assigned === actor);\n\n// Gate #2: stage check (idempotent)\nconst stageAllowed = (currentStage === 'NEW');\n\n// Final allow\nconst authorized = Boolean(authorizedOwner && stageAllowed);\n\n// Reason priority\nlet reason = '';\nif (!authorizedOwner) reason = 'NOT_ASSIGNED_OWNER';\nelse if (!stageAllowed) reason = 'ALREADY_NOT_NEW';\n\nreturn [{\n json: {\n ...payload,\n\n // Flags for the IF node\n authorized_owner: authorizedOwner,\n stage_allowed: stageAllowed,\n authorized,\n\n reason,\n\n assigned_sales_slack_id: assigned,\n current_stage: currentStage,\n\n // Include lead data for better feedback\n lead_name: String(leadRow.name || '').trim(),\n lead_phone: String(leadRow.phone || '').trim(),\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "f14e59b3-6f63-4e8d-8186-8874d463047f",
"name": "\ud83d\udea8 SLACK ESCALATION",
"type": "n8n-nodes-base.slack",
"position": [
3408,
1088
],
"parameters": {
"text": "=",
"select": "channel",
"blocksUi": "={\n \"blocks\": [\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"\ud83d\udea8 *LEAD ESCALATION \u2013 NO RESPONSE*\\n\\nThis lead has been *re-routed {{ $('IF route_count >= 10').item.json.route_count }} times* without being contacted.\\n\\n*Lead ID:* {{ $('IF route_count >= 10').item.json.lead_id }}\\n*Name:* {{ $('IF route_count >= 10').item.json.name }} \\n*Phone:* {{ $('IF route_count >= 10').item.json.phone }}\\n*Last Assigned Sales:* <@{{ $('IF route_count >= 10').item.json.sales_slack_id }}>\\n\\n\u26a0\ufe0f *Action required from manager.*\"\n }\n },\n {\n \"type\": \"context\",\n \"elements\": [\n {\n \"type\": \"mrkdwn\",\n \"text\": \"\ud83d\udc64 Escalated to: <@{{ $('Set Manager id').item.json.manager_slack_id }}>\\n\ud83d\udd52 Escalated at: {{ new Date().toISOString() }}\"\n }\n ]\n }\n ]\n}",
"channelId": {
"__rl": true,
"mode": "id",
"value": "<REDACTED_SLACK_CHANNEL_ID_2>"
},
"messageType": "block",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.4
},
{
"id": "5cbd553a-7bad-4bfc-8adc-98ce6485806c",
"name": "Set Manager id",
"type": "n8n-nodes-base.set",
"position": [
2576,
1088
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "{\n \"manager_slack_id\": \"<REDACTED_MANAGER_SLACK_USER_ID>\"\n}\n",
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "328f94e0-f496-46d4-ae9b-8396788c94d8",
"name": "IF route_count >= 10",
"type": "n8n-nodes-base.if",
"position": [
2352,
1184
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d05144bb-b1f2-48da-ae0d-2cfc067d0c15",
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $json.route_count }}",
"rightValue": 10
}
]
}
},
"typeVersion": 2.3
},
{
"id": "78fdb48e-54b1-47fa-8e75-f65820872006",
"name": "ESC) Get escalation flag",
"type": "n8n-nodes-base.googleSheets",
"position": [
2800,
1088
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ 'escalated_' + $json.lead_id }}",
"lookupColumn": "key"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "routing_state_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7,
"alwaysOutputData": true
},
{
"id": "7a5107c4-1ebd-403a-b6d8-a1d0d793d664",
"name": "ESC) Set escalation flag",
"type": "n8n-nodes-base.googleSheets",
"position": [
3648,
1088
],
"parameters": {
"columns": {
"value": {
"key": "={{ 'escalated_' + $items('SLA) Re-route to next sales')[$itemIndex].json.lead_id }}",
"value": "={{ new Date().toISOString() }}"
},
"schema": [
{
"id": "key",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "key",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "value",
"type": "string",
"display": true,
"required": false,
"displayName": "value",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"key"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 0,
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "routing_state_sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "<REDACTED_GOOGLE_SHEET_DOCUMENT_ID>",
"cachedResultUrl": "<REDACTED_GOOGLE_SHEET_URL>",
"cachedResultName": "DEMO_SHEET"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "12854bb3-1ab6-42a2-ba90-4bb1ee83ddca",
"name": "ESC) Normalize flag result",
"type": "n8n-nodes-base.code",
"position": [
2992,
1088
],
"parameters": {
"jsCode": "// Read the lead from the previous node in the escalation path (must have lead_id)\nconst lead = $json;\n\n// Lookup results from ESC) Get escalation flag\nconst rows = $items(\"ESC) Get escalation flag\").map(i => i.json);\n\n// Consider it found only if there is a non-empty key/value\nconst found = rows.some(r => r && String(r.key || \"\").trim() !== \"\");\n\nreturn [{\n json: {\n ...lead,\n esc_key: `escalated_${lead.lead_id}`,\n already_escalated: found,\n esc_row: found ? rows.find(r => String(r.key || \"\").trim() !== \"\") : null\n }\n}];"
},
"typeVersion": 2
},
{
"id": "cfd92876-0c6f-4e3a-b4dd-26a6981427e9",
"name": "ESC) If not escalated yet",
"type": "n8n-nodes-base.if",
"position": [
3200,
1088
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "5c1b6c7a-acdf-4ea7-bcf4-047bd3a201a7",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.already_escalated === false }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "d2c74230-2458-4143-b7ba-e8a73dc3f9b1",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
112,
512
],
"parameters": {
"width": 752,
"height": 736,
"content": "## SLA-Based Lead Routing & Auto Reassignment Workflow\n\nThis workflow implements an SLA-based lead routing system designed to ensure fast response times and prevent lead neglect through automated reassignment and escalation.\n\nWhen a new lead is submitted, the workflow immediately assigns it to a sales representative using a round-robin distribution. This ensures fair workload allocation across the sales team and avoids manual lead picking.\n\nA 1-hour SLA threshold is applied after each assignment. If the lead remains in the \u201cNEW\u201d stage and has not been contacted within this timeframe, the system automatically re-routes the lead to the next available sales representative in a circular order.\n\nThe SLA duration can be adjusted easily by modifying the Schedule Trigger configuration.\n\nWhile this implementation uses Google Sheets as a lightweight operational datastore and Slack as the interaction layer, the same logic can be adapted to other databases or CRM platforms (such as Salesforce, HubSpot, or PostgreSQL).\n\nA scheduled process periodically scans the lead database and identifies leads that meet the following conditions:\n\na) Lead stage is NEW\nb) No contact activity has occurred within the SLA window\n\nQualified leads are automatically re-assigned, and Slack notifications are sent to the newly assigned sales representative with an interactive \u201cMark as CONTACTED\u201d action.\n\nTo maintain operational safety:\n\na) Only the currently assigned sales representative is allowed to update the lead status\nb) Stage updates are idempotent, preventing duplicate or accidental re-processing\nc) Repeated SLA breaches trigger a one-time escalation to a manager, ensuring visibility without notification spam\n\nThis workflow helps improve response speed, enforces consistent follow-up behavior, and introduces operational discipline without requiring a full CRM system."
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "<REDACTED_WORKFLOW_VERSION_ID>",
"connections": {
"2) Prep Lead": {
"main": [
[
{
"node": "3) Get Sales List (active=ON)",
"type": "main",
"index": 0
}
]
]
},
"Set Manager id": {
"main": [
[
{
"node": "ESC) Get escalation flag",
"type": "main",
"index": 0
}
]
]
},
"4) Build Sales List": {
"main": [
[
{
"node": "5) Get routing_state (last_index)",
"type": "main",
"index": 0
}
]
]
},
"SLA) Get Sales List": {
"main": [
[
{
"node": "SLA) Build Sales List",
"type": "main",
"index": 0
}
]
]
},
"STAGE) Prep Payload": {
"main": [
[
{
"node": "STAGE) Lookup lead by lead_id",
"type": "main",
"index": 0
}
]
]
},
"IF route_count >= 10": {
"main": [
[
{
"node": "Set Manager id",
"type": "main",
"index": 0
}
],
[
{
"node": "SLA) Update lead (after reroute)",
"type": "main",
"index": 0
}
]
]
},
"STAGE) If authorized": {
"main": [
[
{
"node": "STAGE) Update lead stage to CONTACTED",
"type": "main",
"index": 0
}
],
[
{
"node": "STAGE) Slack Feedback - Not allowed",
"type": "main",
"index": 0
}
]
]
},
"SLA) Build Sales List": {
"main": [
[
{
"node": "SLA) Get Leads (stage=NEW)",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udea8 SLACK ESCALATION": {
"main": [
[
{
"node": "ESC) Set escalation flag",
"type": "main",
"index": 0
}
]
]
},
"7) Update routing_state": {
"main": [
[]
]
},
"ESC) Get escalation flag": {
"main": [
[
{
"node": "ESC) Normalize flag result",
"type": "main",
"index": 0
}
]
]
},
"ESC) If not escalated yet": {
"main": [
[
{
"node": "\ud83d\udea8 SLACK ESCALATION",
"type": "main",
"index": 0
}
],
[]
]
},
"1) Form Trigger (New Lead)": {
"main": [
[
{
"node": "2) Prep Lead",
"type": "main",
"index": 0
}
]
]
},
"ESC) Normalize flag result": {
"main": [
[
{
"node": "ESC) If not escalated yet",
"type": "main",
"index": 0
}
]
]
},
"SLA Trigger (every 1 hour)": {
"main": [
[
{
"node": "SLA) Get Sales List",
"type": "main",
"index": 0
}
]
]
},
"SLA) Get Leads (stage=NEW)": {
"main": [
[
{
"node": "SLA) If last route >= 1 hour",
"type": "main",
"index": 0
}
]
]
},
"SLA) Re-route to next sales": {
"main": [
[
{
"node": "IF route_count >= 10",
"type": "main",
"index": 0
}
]
]
},
"8) Upsert Lead (leads sheet)": {
"main": [
[
{
"node": "9) Slack Notify (New Lead) & Button",
"type": "main",
"index": 0
}
]
]
},
"SLA) If last route >= 1 hour": {
"main": [
[
{
"node": "SLA) Re-route to next sales",
"type": "main",
"index": 0
}
],
[]
]
},
"3) Get Sales List (active=ON)": {
"main": [
[
{
"node": "4) Build Sales List",
"type": "main",
"index": 0
}
]
]
},
"STAGE) Lookup lead by lead_id": {
"main": [
[
{
"node": "STAGE) Authorize click (must be assigned sales)",
"type": "main",
"index": 0
}
]
]
},
"6) Initial Assign (Round Robin)": {
"main": [
[
{
"node": "7) Update routing_state",
"type": "main",
"index": 0
},
{
"node": "8) Upsert Lead (leads sheet)",
"type": "main",
"index": 0
}
]
]
},
"SLA) Update lead (after reroute)": {
"main": [
[
{
"node": "SLA) Slack Notify (reroute) & Button",
"type": "main",
"index": 0
}
]
]
},
"5) Get routing_state (last_index)": {
"main": [
[
{
"node": "6) Initial Assign (Round Robin)",
"type": "main",
"index": 0
}
]
]
},
"STAGE) Webhook (/demo-stage-update)": {
"main": [
[
{
"node": "STAGE) Prep Payload",
"type": "main",
"index": 0
}
]
]
},
"SLA) Slack Notify (reroute) & Button": {
"main": [
[]
]
},
"STAGE) Update lead stage to CONTACTED": {
"main": [
[]
]
},
"STAGE) Authorize click (must be assigned sales)": {
"main": [
[
{
"node": "STAGE) If authorized",
"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.
googleSheetsOAuth2ApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This repository contains an SLA-based lead routing workflow built in n8n, designed to ensure fast lead response, fair sales distribution, and controlled escalation without relying on a full CRM system.
Source: https://n8n.io/workflows/12702/ — 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.
Find companies similar to your best clients using PredictLeads, enrich each with news, hiring, and tech signals, then score them 0–100 for outreach priority.
How it works A form trigger accepts an Industry + Location query (e.g. Accountants London). Text Search Page 1 calls Google Places Text Search to return results and a nextpagetoken. Conditional checks
Agencies, sales teams, and service businesses who want to instantly qualify inbound leads with an AI-powered phone call — no manual follow-up needed.
This n8n template automates lead generation by scraping Google Maps using the Olostep API. It extracts business names, locations, websites, phone numbers, and decision-maker names (CEO, Founder, etc.)
This n8n template automates finding roofing contractors in any city using Google Maps. It deep-scrapes listings via ScrapeOps Proxy, deduplicates results against Google Sheets, saves fresh leads, and