This workflow follows the Agent → 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 →
{
"name": "WhatsApp \u2192 AlfaCRM Sales Funnel (Main, v2 Advanced)",
"nodes": [
{
"parameters": {
"content": "## \ud83d\udce5 1. Webhook + signature\n\nEvolution API \u0448\u043b\u0451\u0442 `messages.upsert` \u0441\u044e\u0434\u0430. \u0423\u0437\u0435\u043b `Verify Signature` \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 header `apikey` (\u0438\u043b\u0438 HMAC-\u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 \u0434\u043b\u044f \u043f\u0440\u043e\u0434-\u043a\u043e\u043d\u0444\u0438\u0433\u043e\u0432) \u0438 \u0440\u0435\u0436\u0435\u0442 \u043f\u043e\u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b 401-\u044b\u043c.\n\n**Path:** `/webhook/evolution-incoming`\n\n\u041f\u0440\u043e\u0442\u0438\u0432 webhook-bombing: \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 Caddyfile \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d rate_limit 60/min/IP.",
"height": 280,
"width": 360,
"color": 4
},
"id": "note-webhook",
"name": "Note: Webhook",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-220,
-120
]
},
{
"parameters": {
"content": "## \ud83c\udfac 2. Multimodal routing\n\n\u041a\u043b\u0438\u0435\u043d\u0442 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0441\u043b\u0430\u0442\u044c **\u0442\u0435\u043a\u0441\u0442 / \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0435 / \u0444\u043e\u0442\u043e**. \u041c\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0442\u0438\u043f, \u0438:\n- text \u2192 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c `conversation` \u0438\u043b\u0438 `extendedTextMessage.text`\n- audio \u2192 \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u043c base64 \u2192 OpenAI Whisper STT \u2192 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0442\u0440\u0430\u043d\u0441\u043a\u0440\u0438\u043f\u0442\n- image \u2192 \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u043c base64 \u2192 GPT-4o vision \u2192 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 caption + \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u043d\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\n\n\u042d\u0442\u043e \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0431\u043e\u0442\u0430 \u0432 multimodal-\u0430\u0433\u0435\u043d\u0442\u0430, \u0430 \u043d\u0435 \u0432 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0447\u0430\u0442.",
"height": 320,
"width": 460,
"color": 5
},
"id": "note-multimodal",
"name": "Note: Multimodal",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
600,
-120
]
},
{
"parameters": {
"content": "## \ud83d\udcbe 3. Human-like \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u044f\n\n\u0415\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0438\u0448\u0435\u0442 3 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u0434\u0440\u044f\u0434 \u0437\u0430 4 \u0441\u0435\u043a\u0443\u043d\u0434\u044b \u2014 \u043c\u044b \u041d\u0415 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c 3 \u043e\u0442\u0432\u0435\u0442\u0430. Sub-workflow `12_buffer_messages` \u0434\u0435\u0431\u0430\u0443\u043d\u0441\u0438\u0442 6 \u0441\u0435\u043a\u0443\u043d\u0434 \u0438 \u0441\u043a\u043b\u0435\u0438\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u043e\u0434\u0438\u043d \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f AI.\n\n\u041f\u043e\u0431\u0435\u0434\u0438\u0442\u0435\u043b\u044c \u0433\u043e\u043d\u043a\u0438 (lock \u0432 Redis) \u0437\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u0431\u0443\u0444\u0435\u0440 \u0438 \u0438\u0434\u0451\u0442 \u0434\u0430\u043b\u044c\u0448\u0435. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 exit.",
"height": 280,
"width": 460,
"color": 6
},
"id": "note-buffer",
"name": "Note: Buffer",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1700,
-120
]
},
{
"parameters": {
"content": "## \ud83d\udd10 4. AlfaCRM (auth \u043a\u044d\u0448 + find/create)\n\nAuth \u0438\u0434\u0451\u0442 \u0447\u0435\u0440\u0435\u0437 sub-workflow `10_alfacrm_auth_cached` \u2014 Redis-\u043a\u044d\u0448 \u043d\u0430 3500s. \u041e\u0434\u0438\u043d \u0442\u043e\u043a\u0435\u043d \u043d\u0430 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u0442\u044b\u0441\u044f\u0447 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439.\n\n`/customer/index` \u0438\u0449\u0435\u0442 \u043f\u043e `phone`. \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u2014 \u0431\u0435\u0440\u0451\u043c id; \u043d\u0435\u0442 \u2014 `customer/create` \u0441 \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u043c lead_status_id (\u044d\u0442\u0430\u043f 1, \u043f\u0435\u0440\u0432\u0438\u0447\u043d\u044b\u0439) \u0438 lead_source_id \u0438\u0437 env.",
"height": 280,
"width": 460,
"color": 5
},
"id": "note-alfacrm",
"name": "Note: AlfaCRM",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
2820,
-120
]
},
{
"parameters": {
"content": "## \ud83e\udd16 5. AI-\u0430\u0433\u0435\u043d\u0442 \u043a\u0432\u0430\u043b\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438\n\nLangChain Tools Agent + OpenAI Chat Model + Postgres Chat Memory.\n\n**Memory:** Postgres-backed history \u043f\u043e `sessionId = wa:<phone>` \u2014 \u0430\u0433\u0435\u043d\u0442 \u043f\u043e\u043c\u043d\u0438\u0442 \u0432\u0435\u0441\u044c \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439 \u0434\u0438\u0430\u043b\u043e\u0433.\n\n**System prompt:** \u0436\u0451\u0441\u0442\u043a\u043e \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u0442 \u0440\u043e\u043b\u044c (sales SDR) + 4-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u043a\u0432\u0430\u043b\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e (\u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044c / \u0431\u044e\u0434\u0436\u0435\u0442 / \u0441\u0440\u043e\u043a\u0438 / \u041b\u041f\u0420) + JSON-\u0441\u0445\u0435\u043c\u0443 \u043e\u0442\u0432\u0435\u0442\u0430.\n\n**Output validator:** Structured Output Parser \u0440\u0435\u0436\u0435\u0442 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439 JSON \u2014 \u043d\u0430 \u043f\u0440\u043e\u0434\u0435 \u0441\u0442\u0430\u0432\u0438\u043c Auto-fixing Output Parser \u0441\u0432\u0435\u0440\u0445\u0443 \u0434\u043b\u044f \u0441\u0430\u043c\u043e\u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f.\n\nLLM swap: \u043c\u0435\u043d\u044f\u0435\u043c `OpenAI Chat Model` \u043d\u0430 `Google Gemini Chat Model` \u0438\u043b\u0438 `Anthropic Chat Model` \u0431\u0435\u0437 \u043a\u0430\u0441\u0430\u043d\u0438\u044f \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438.",
"height": 380,
"width": 460,
"color": 3
},
"id": "note-ai",
"name": "Note: AI",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
3940,
-120
]
},
{
"parameters": {
"content": "## \ud83c\udfaf 6. \u041c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439\n\nAI \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 `action`:\n- `continue` \u2014 \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0443\n- `move_stage` \u2014 \u0434\u0432\u0438\u0433\u0430\u0435\u043c \u0441\u0434\u0435\u043b\u043a\u0443 \u0432 AlfaCRM \u043d\u0430 `newStageId` + \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u043c\n- `handoff` \u2014 \u0433\u043e\u0440\u044f\u0447\u0438\u0439 \u043b\u0438\u0434: \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c sub-workflow `21_hot_lead_handoff` (Telegram-\u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0443) + \u0434\u0432\u0438\u0433\u0430\u0435\u043c \u0441\u0442\u0430\u0434\u0438\u044e + \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u043c\n\nFallback (\u043d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 JSON \u043e\u0442 LLM) \u2192 `continue` \u0441 \u043e\u0442\u0432\u0435\u0442\u043e\u043c \u00ab\u0443\u0442\u043e\u0447\u043d\u0438\u0442\u0435, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430\u00bb.",
"height": 300,
"width": 460,
"color": 2
},
"id": "note-route",
"name": "Note: Routing",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
5060,
-120
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "evolution-incoming",
"responseMode": "responseNode",
"options": {}
},
"id": "wh",
"name": "Evolution Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-220,
320
]
},
{
"parameters": {
"jsCode": "// Verify webhook authenticity.\n// Evolution API allows static apikey header OR HMAC-SHA256 over the body (when WEBHOOK_HMAC_KEY is set).\n// We accept either; reject if neither matches.\nconst headers = $input.first().json.headers || {};\nconst body = $input.first().json.body;\nconst expected = $env.EVOLUTION_API_KEY;\nconst hmacKey = $env.EVOLUTION_HMAC_KEY;\n\nconst providedKey = headers['apikey'] || headers['x-apikey'];\nlet okApiKey = expected && providedKey && providedKey === expected;\n\nlet okHmac = false;\nif (hmacKey && headers['x-hub-signature-256']) {\n const crypto = require('crypto');\n const sig = 'sha256=' + crypto.createHmac('sha256', hmacKey).update(JSON.stringify(body)).digest('hex');\n okHmac = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(headers['x-hub-signature-256']));\n}\n\nreturn [{ json: { ...body, _verified: okApiKey || okHmac, _ip: headers['x-real-ip'] || headers['x-forwarded-for'] || 'unknown' } }];"
},
"id": "verify",
"name": "Verify Signature",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
0,
320
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "v",
"leftValue": "={{ $json._verified }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-verify",
"name": "Verified?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
220,
320
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={ \"error\": \"Unauthorized webhook\" }",
"options": {
"responseCode": 401
}
},
"id": "resp-401",
"name": "Respond 401",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
440,
480
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.event }}",
"rightValue": "messages.upsert",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "c2",
"leftValue": "={{ $json.data?.key?.fromMe || false }}",
"rightValue": false,
"operator": {
"type": "boolean",
"operation": "equals",
"singleValue": true
}
},
{
"id": "c3",
"leftValue": "={{ ($json.data?.key?.remoteJid || '').endsWith('@g.us') }}",
"rightValue": false,
"operator": {
"type": "boolean",
"operation": "equals",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "filter",
"name": "Personal Incoming Msg?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
440,
280
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={ \"ok\": true, \"skipped\": \"not personal\" }",
"options": {}
},
"id": "resp-skip",
"name": "Respond Skip",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
660,
460
]
},
{
"parameters": {
"jsCode": "// Determine message type & extract media reference if needed.\nconst d = $json.data || {};\nconst msg = d.message || {};\nconst type = d.messageType || (msg.conversation ? 'conversation' : msg.imageMessage ? 'imageMessage' : msg.audioMessage ? 'audioMessage' : 'other');\n\nlet category = 'text';\nif (type === 'audioMessage' || msg.audioMessage) category = 'audio';\nelse if (type === 'imageMessage' || msg.imageMessage) category = 'image';\nelse if (msg.conversation || msg.extendedTextMessage) category = 'text';\nelse category = 'other';\n\nconst text = msg.conversation || msg.extendedTextMessage?.text || msg.imageMessage?.caption || '';\nconst phone = (d.key?.remoteJid || '').split('@')[0];\n\nreturn [{\n json: {\n category,\n rawType: type,\n text,\n phone,\n phoneE164: '+' + phone.replace(/[^\\d]/g, ''),\n pushName: d.pushName || phone,\n instance: $('Verify Signature').item.json.instance,\n messageId: d.key?.id,\n mediaKey: msg.audioMessage?.mediaKey || msg.imageMessage?.mediaKey || null,\n mediaUrl: msg.audioMessage?.url || msg.imageMessage?.url || null,\n raw: $json\n }\n}];"
},
"id": "router",
"name": "Detect Message Type",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
280
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"id": "t",
"leftValue": "={{ $json.category }}",
"rightValue": "text",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "text"
},
{
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"id": "a",
"leftValue": "={{ $json.category }}",
"rightValue": "audio",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "audio"
},
{
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"id": "i",
"leftValue": "={{ $json.category }}",
"rightValue": "image",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "image"
}
]
},
"options": {
"fallbackOutput": "extra",
"renameFallbackOutput": "other"
}
},
"id": "switch-type",
"name": "By Message Type",
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
880,
280
]
},
{
"parameters": {
"method": "GET",
"url": "=http://evolution-api:8080/chat/getBase64FromMediaMessage/{{ $json.instance }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "apikey",
"value": "={{ $env.EVOLUTION_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyContentType": "json",
"specifyBody": "json",
"jsonBody": "={ \"message\": {{ JSON.stringify($json.raw.data.message) }}, \"convertToMp4\": false }",
"options": {
"timeout": 30000
}
},
"id": "audio-fetch",
"name": "Evolution: Get Audio",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1100,
200
]
},
{
"parameters": {
"resource": "audio",
"operation": "transcribe",
"binaryPropertyName": "data",
"options": {
"language": "ru"
}
},
"id": "whisper",
"name": "Whisper STT",
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 1.6,
"position": [
1320,
200
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "x1",
"name": "text",
"value": "={{ $json.text }}",
"type": "string"
},
{
"id": "x2",
"name": "phone",
"value": "={{ $('Detect Message Type').item.json.phone }}",
"type": "string"
},
{
"id": "x3",
"name": "phoneE164",
"value": "={{ $('Detect Message Type').item.json.phoneE164 }}",
"type": "string"
},
{
"id": "x4",
"name": "pushName",
"value": "={{ $('Detect Message Type').item.json.pushName }}",
"type": "string"
},
{
"id": "x5",
"name": "instance",
"value": "={{ $('Detect Message Type').item.json.instance }}",
"type": "string"
},
{
"id": "x6",
"name": "messageId",
"value": "={{ $('Detect Message Type').item.json.messageId }}",
"type": "string"
},
{
"id": "x7",
"name": "originalType",
"value": "audio",
"type": "string"
}
]
},
"options": {}
},
"id": "audio-out",
"name": "Audio \u2192 Text",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1540,
200
]
},
{
"parameters": {
"method": "GET",
"url": "=http://evolution-api:8080/chat/getBase64FromMediaMessage/{{ $json.instance }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "apikey",
"value": "={{ $env.EVOLUTION_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyContentType": "json",
"specifyBody": "json",
"jsonBody": "={ \"message\": {{ JSON.stringify($json.raw.data.message) }}, \"convertToMp4\": false }",
"options": {
"timeout": 30000
}
},
"id": "image-fetch",
"name": "Evolution: Get Image",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1100,
380
]
},
{
"parameters": {
"resource": "image",
"operation": "analyze",
"modelId": {
"__rl": true,
"value": "gpt-4o-mini",
"mode": "list"
},
"text": "\u041e\u043f\u0438\u0448\u0438, \u0447\u0442\u043e \u043d\u0430 \u044d\u0442\u043e\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0438. \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0442\u0435\u043a\u0441\u0442 \u2014 \u043f\u0440\u0438\u0432\u0435\u0434\u0438 \u0435\u0433\u043e \u0434\u043e\u0441\u043b\u043e\u0432\u043d\u043e. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0438\u043b\u0438 \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u2014 \u0438\u0437\u0432\u043b\u0435\u043a\u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 (\u0447\u0438\u0441\u043b\u0430, \u0434\u0430\u0442\u044b, \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b). \u041e\u0442\u0432\u0435\u0442\u044c \u043f\u043e-\u0440\u0443\u0441\u0441\u043a\u0438, \u043a\u0440\u0430\u0442\u043a\u043e (1-3 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u044f).",
"inputType": "base64",
"binaryPropertyName": "data",
"options": {}
},
"id": "vision",
"name": "GPT-4o Vision",
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 1.6,
"position": [
1320,
380
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "i1",
"name": "text",
"value": "=[\u041f\u0440\u0438\u0441\u043b\u0430\u043b \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435] {{ $json.content || $json.message?.content || $json.choices?.[0]?.message?.content }}",
"type": "string"
},
{
"id": "i2",
"name": "phone",
"value": "={{ $('Detect Message Type').item.json.phone }}",
"type": "string"
},
{
"id": "i3",
"name": "phoneE164",
"value": "={{ $('Detect Message Type').item.json.phoneE164 }}",
"type": "string"
},
{
"id": "i4",
"name": "pushName",
"value": "={{ $('Detect Message Type').item.json.pushName }}",
"type": "string"
},
{
"id": "i5",
"name": "instance",
"value": "={{ $('Detect Message Type').item.json.instance }}",
"type": "string"
},
{
"id": "i6",
"name": "messageId",
"value": "={{ $('Detect Message Type').item.json.messageId }}",
"type": "string"
},
{
"id": "i7",
"name": "originalType",
"value": "image",
"type": "string"
}
]
},
"options": {}
},
"id": "image-out",
"name": "Image \u2192 Caption",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1540,
380
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "t1",
"name": "text",
"value": "={{ $json.text }}",
"type": "string"
},
{
"id": "t2",
"name": "phone",
"value": "={{ $json.phone }}",
"type": "string"
},
{
"id": "t3",
"name": "phoneE164",
"value": "={{ $json.phoneE164 }}",
"type": "string"
},
{
"id": "t4",
"name": "pushName",
"value": "={{ $json.pushName }}",
"type": "string"
},
{
"id": "t5",
"name": "instance",
"value": "={{ $json.instance }}",
"type": "string"
},
{
"id": "t6",
"name": "messageId",
"value": "={{ $json.messageId }}",
"type": "string"
},
{
"id": "t7",
"name": "originalType",
"value": "text",
"type": "string"
}
]
},
"options": {}
},
"id": "text-out",
"name": "Text passthrough",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1100,
80
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "o1",
"name": "text",
"value": "[\u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f]",
"type": "string"
},
{
"id": "o2",
"name": "phone",
"value": "={{ $('Detect Message Type').item.json.phone }}",
"type": "string"
},
{
"id": "o3",
"name": "phoneE164",
"value": "={{ $('Detect Message Type').item.json.phoneE164 }}",
"type": "string"
},
{
"id": "o4",
"name": "pushName",
"value": "={{ $('Detect Message Type').item.json.pushName }}",
"type": "string"
},
{
"id": "o5",
"name": "instance",
"value": "={{ $('Detect Message Type').item.json.instance }}",
"type": "string"
},
{
"id": "o6",
"name": "messageId",
"value": "={{ $('Detect Message Type').item.json.messageId }}",
"type": "string"
},
{
"id": "o7",
"name": "originalType",
"value": "other",
"type": "string"
}
]
},
"options": {}
},
"id": "other-out",
"name": "Other passthrough",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1100,
540
]
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineByPosition",
"options": {}
},
"id": "merge-types",
"name": "Merge: Normalized",
"type": "n8n-nodes-base.merge",
"typeVersion": 3,
"position": [
1760,
280
]
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "buffermessages0001",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"phone": "={{ $json.phone }}",
"messageText": "={{ $json.text }}",
"messageId": "={{ $json.messageId }}"
},
"schema": [
{
"id": "phone",
"displayName": "phone",
"required": true,
"type": "string"
},
{
"id": "messageText",
"displayName": "messageText",
"required": true,
"type": "string"
},
{
"id": "messageId",
"displayName": "messageId",
"required": true,
"type": "string"
}
]
}
},
"id": "buf",
"name": "Buffer: Debounce 6s",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
1980,
280
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "won",
"leftValue": "={{ $json.won }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-won",
"name": "Won debounce?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
2200,
280
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={ \"ok\": true, \"skipped\": \"newer message took over\" }",
"options": {}
},
"id": "resp-debounced",
"name": "Respond Skip Debounce",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
2420,
460
]
},
{
"parameters": {
"jsCode": "// Combine debounced messages with normalized contact info.\nconst buf = $('Buffer: Debounce 6s').item.json;\nconst contact = $('Merge: Normalized').item.json;\nreturn [{\n json: {\n ...contact,\n text: buf.combinedMessage || contact.text,\n bufferedCount: buf.messageCount || 1\n }\n}];"
},
"id": "post-buf",
"name": "Apply Buffered Text",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2420,
280
]
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "alfacrmauth0001x",
"mode": "id"
}
},
"id": "auth",
"name": "AlfaCRM: Auth (cached)",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
2640,
280
]
},
{
"parameters": {
"jsCode": "// Carry both auth output AND original normalized fields forward.\nconst auth = $json;\nconst ctx = $('Apply Buffered Text').item.json;\nreturn [{ json: { ...ctx, alfacrmToken: auth.token } }];"
},
"id": "post-auth",
"name": "Carry Token + Ctx",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2860,
280
]
},
{
"parameters": {
"method": "POST",
"url": "=https://{{ $env.ALFACRM_HOSTNAME }}/v2api/{{ $env.ALFACRM_BRANCH_ID }}/customer/index",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-ALFACRM-TOKEN",
"value": "={{ $json.alfacrmToken }}"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Accept",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyContentType": "json",
"specifyBody": "json",
"jsonBody": "={\n \"phone\": \"{{ $json.phoneE164 }}\",\n \"page\": 0\n}",
"options": {
"retry": {
"maxTries": 3,
"waitBetweenTries": 500
}
}
},
"id": "find",
"name": "AlfaCRM: Find Customer",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3080,
280
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "exists",
"leftValue": "={{ $json.total || 0 }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-cust",
"name": "Customer Exists?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
3300,
280
]
},
{
"parameters": {
"method": "POST",
"url": "=https://{{ $env.ALFACRM_HOSTNAME }}/v2api/{{ $env.ALFACRM_BRANCH_ID }}/customer/create",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-ALFACRM-TOKEN",
"value": "={{ $('Carry Token + Ctx').item.json.alfacrmToken }}"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Accept",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyContentType": "json",
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"{{ $('Carry Token + Ctx').item.json.pushName }}\",\n \"phone\": [\"{{ $('Carry Token + Ctx').item.json.phoneE164 }}\"],\n \"legal_type\": 1,\n \"is_study\": 0,\n \"lead_status_id\": {{ $env.ALFACRM_DEFAULT_LEAD_STATUS_ID || 1 }},\n \"lead_source_id\": {{ $env.ALFACRM_WHATSAPP_LEAD_SOURCE_ID || 1 }},\n \"branch_ids\": [{{ $env.ALFACRM_BRANCH_ID }}],\n \"note\": \"WhatsApp \u043b\u0438\u0434. \u041f\u0435\u0440\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435: {{ $('Carry Token + Ctx').item.json.text }}\"\n}",
"options": {
"retry": {
"maxTries": 3,
"waitBetweenTries": 500
}
}
},
"id": "create",
"name": "AlfaCRM: Create Customer",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3520,
460
]
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineAll",
"options": {}
},
"id": "merge-cust",
"name": "Merge: Customer Ctx",
"type": "n8n-nodes-base.merge",
"typeVersion": 3,
"position": [
3740,
360
]
},
{
"parameters": {
"jsCode": "const ctx = $('Carry Token + Ctx').item.json;\nlet customerId, currentStage;\n\nconst found = $('AlfaCRM: Find Customer').item.json;\nif ((found.total || 0) > 0) {\n customerId = found.items[0].id;\n currentStage = found.items[0].lead_status_id;\n} else {\n const created = $('AlfaCRM: Create Customer').item.json;\n customerId = created.model?.id || created.id;\n currentStage = created.model?.lead_status_id || 1;\n}\n\nreturn [{\n json: {\n ...ctx,\n customerId,\n currentStageId: currentStage,\n sessionId: `wa:${ctx.phoneE164}`\n }\n}];"
},
"id": "build-ctx",
"name": "Build Lead Context",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3960,
360
]
},
{
"parameters": {
"promptType": "define",
"text": "=\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 ({{ $json.pushName }}, {{ $json.phoneE164 }}):\n\"\"\"\n{{ $json.text }}\n\"\"\"\n\n\u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u044d\u0442\u0430\u043f \u0432 CRM: {{ $json.currentStageId }}.\n\u0422\u0438\u043f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f: {{ $json.originalType }}.\n\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043a\u043b\u0435\u0435\u043d\u043d\u044b\u0445 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445: {{ $json.bufferedCount }}.",
"hasOutputParser": false,
"options": {
"systemMessage": "\u0422\u044b \u2014 AI-\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442 \u043e\u0442\u0434\u0435\u043b\u0430 \u043f\u0440\u043e\u0434\u0430\u0436. \u0422\u0432\u043e\u044f \u0437\u0430\u0434\u0430\u0447\u0430:\n1. \u041e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u043d\u0430 WhatsApp-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432\u0435\u0436\u043b\u0438\u0432\u043e \u0438 \u043a\u0440\u0430\u0442\u043a\u043e (2-4 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u044f).\n2. \u041a\u0432\u0430\u043b\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0434 \u043f\u043e 4 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c (BANT-light): \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044c, \u0431\u044e\u0434\u0436\u0435\u0442, \u0441\u0440\u043e\u043a\u0438, \u041b\u041f\u0420.\n3. \u041f\u043e \u043c\u0435\u0440\u0435 \u0432\u044b\u044f\u0441\u043d\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442\u044c \u0441\u0434\u0435\u043b\u043a\u0443 \u043f\u043e \u044d\u0442\u0430\u043f\u0430\u043c \u0432\u043e\u0440\u043e\u043d\u043a\u0438.\n\n\u042d\u0442\u0430\u043f\u044b \u0432\u043e\u0440\u043e\u043d\u043a\u0438 (lead_status_id):\n- 1: \u041d\u043e\u0432\u044b\u0439 \u043b\u0438\u0434 (\u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u0440\u0430\u0442\u0438\u043b\u0441\u044f)\n- 2: \u0412 \u0440\u0430\u0431\u043e\u0442\u0435 (\u0432\u044b\u044f\u0441\u043d\u0438\u043b\u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044c)\n- 3: \u041a\u0432\u0430\u043b\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d (\u0435\u0441\u0442\u044c \u0431\u044e\u0434\u0436\u0435\u0442/\u0441\u0440\u043e\u043a\u0438/\u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u044b)\n- 4: \u0413\u043e\u0440\u044f\u0447\u0438\u0439 (\u0433\u043e\u0442\u043e\u0432 \u043e\u043f\u043b\u0430\u0447\u0438\u0432\u0430\u0442\u044c / \u043f\u0440\u043e\u0441\u0438\u0442 \u0441\u0447\u0451\u0442 / \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b)\n- 5: \u041d\u0435 \u0446\u0435\u043b\u0435\u0432\u043e\u0439 (\u043d\u0435 \u043d\u0430\u0448\u0430 \u0426\u0410, \u043e\u0442\u043a\u0430\u0437, \u043c\u0443\u0441\u043e\u0440)\n\n\u0412\u0441\u0435\u0433\u0434\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0439 \u0421\u0422\u0420\u041e\u0413\u041e JSON \u0431\u0435\u0437 markdown, \u0431\u0435\u0437 \\\"```json\\\", \u0431\u0435\u0437 \u043f\u0440\u0435\u0430\u043c\u0431\u0443\u043b\u044b:\n{\n \"reply\": \"<\u0442\u0435\u043a\u0441\u0442 \u043e\u0442\u0432\u0435\u0442\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c>\",\n \"action\": \"continue\" | \"move_stage\" | \"handoff\",\n \"newStageId\": <number \u0438\u043b\u0438 null \u0435\u0441\u043b\u0438 continue>,\n \"reason\": \"<\u043a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u0435 \u0442\u0432\u043e\u0435\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f>\"\n}\n\n\u041f\u0440\u0430\u0432\u0438\u043b\u0430:\n- continue \u2014 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u0431\u0435\u0437 \u0441\u043c\u0435\u043d\u044b \u044d\u0442\u0430\u043f\u0430\n- move_stage \u2014 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u043c \u043d\u0430 \u043d\u043e\u0432\u044b\u0439 \u044d\u0442\u0430\u043f (\u0443\u043a\u0430\u0436\u0438 newStageId)\n- handoff \u2014 \u0433\u043e\u0440\u044f\u0447\u0438\u0439 \u043b\u0438\u0434 \u043d\u0430 4-\u043c \u044d\u0442\u0430\u043f\u0435, \u043d\u0443\u0436\u0435\u043d \u0436\u0438\u0432\u043e\u0439 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 (newStageId: 4)\n- \u0415\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0438\u0448\u0435\u0442 \u043c\u0443\u0441\u043e\u0440/\u0441\u043f\u0430\u043c/\u043d\u0435 \u043f\u043e \u0442\u0435\u043c\u0435 \u2014 move_stage \u043d\u0430 5\n- \u041d\u0435 \u0432\u044b\u0434\u0443\u043c\u044b\u0432\u0430\u0439 \u0444\u0430\u043a\u0442\u044b \u043e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0435 \u0437\u043d\u0430\u0435\u0448\u044c \u2014 \u0441\u043f\u0440\u043e\u0441\u0438 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430"
}
},
"id": "ai",
"name": "AI: Lead Qualifier",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 1.7,
"position": [
4180,
360
]
},
{
"parameters": {
"model": {
"__rl": true,
"value": "gpt-4o-mini",
"mode": "list"
},
"options": {
"temperature": 0.3,
"maxTokens": 400
}
},
"id": "llm",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [
4080,
560
]
},
{
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ $('Build Lead Context').item.json.sessionId }}",
"tableName": "n8n_chat_histories",
"contextWindowLength": 20
},
"id": "memory",
"name": "Postgres Chat Memory",
"type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
"typeVersion": 1.3,
"position": [
4280,
560
]
},
{
"parameters": {
"jsCode": "// Robust AI output parsing with auto-recovery.\nconst raw = $json.output || $json.text || JSON.stringify($json);\n\nlet parsed;\n// Strip markdown fences if model wrapped JSON\nconst clean = raw.replace(/```json\\s*|\\s*```/g, '').trim();\ntry {\n parsed = JSON.parse(clean);\n} catch {\n // Try to extract first JSON object\n const m = clean.match(/\\{[\\s\\S]*\\}/);\n if (m) {\n try { parsed = JSON.parse(m[0]); } catch { parsed = null; }\n }\n}\n\nif (!parsed || !parsed.reply) {\n parsed = {\n reply: '\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u043c\u043e\u0433\u0443 \u0443\u0442\u043e\u0447\u043d\u0438\u0442\u044c \u0432\u0430\u0448 \u0437\u0430\u043f\u0440\u043e\u0441?',\n action: 'continue',\n newStageId: null,\n reason: 'AI returned non-JSON or empty output'\n };\n}\n\n// Always carry context forward\nconst ctx = $('Build Lead Context').item.json;\nreturn [{\n json: {\n ...ctx,\n aiReply: parsed.reply,\n aiAction: parsed.action || 'continue',\n aiNewStageId: parsed.newStageId,\n aiReason: parsed.reason\n }\n}];"
},
"id": "parse",
"name": "Parse AI Output",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4720,
360
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"id": "h",
"leftValue": "={{ $json.aiAction }}",
"rightValue": "handoff",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "handoff"
},
{
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"id": "ms",
"leftValue": "={{ $json.aiAction }}",
"rightValue": "move_stage",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "move_stage"
}
]
},
"options": {
"fallbackOutput": "extra",
"renameFallbackOutput": "continue"
}
},
"id": "switch",
"name": "Route by Action",
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
4940,
360
]
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "hotleadhandoff1",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"customerId": "={{ $json.customerId }}",
"phone": "={{ $json.phoneE164 }}",
"sessionId": "={{ $json.sessionId }}",
"reason": "={{ $json.aiReason }}",
"alfacrmToken": "={{ $json.alfacrmToken }}"
}
}
},
"id": "handoff",
"name": "Hot Lead \u2192 Telegram",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
5160,
200
]
},
{
"parameters": {
"method": "POST",
"url": "=https://{{ $env.ALFACRM_HOSTNAME }}/v2api/{{ $env.ALFACRM_BRANCH_ID }}/customer/update?id={{ $json.customerId }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-ALFACRM-TOKEN",
"value": "={{ $json.alfacrmToken }}"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Accept",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyContentType": "json",
"specifyBody": "json",
"jsonBody": "={\n \"lead_status_id\": {{ $json.aiNewStageId }},\n \"note\": \"AI move: {{ $json.aiReason }}\"\n}",
"options": {
"retry": {
"maxTries": 3,
"waitBetweenTries": 500
}
}
},
"id": "update",
"name": "AlfaCRM: Update Stage",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5380,
360
]
},
{
"parameters": {
"method": "POST",
"url": "=http://evolution-api:8080/message/sendText/{{ $json.instance }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "apikey",
"value": "={{ $env.EVOLUTION_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyContentType": "json",
"specifyBody": "json",
"jsonBody": "={\n \"number\": \"{{ $json.phoneE164 }}\",\n \"text\": {{ JSON.stringify($json.aiReply) }},\n \"delay\": 1000\n}",
"options": {
"retry": {
"maxTries": 3,
"waitBetweenTries": 1500
}
}
},
"id": "reply",
"name": "Evolution: Send Reply",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5600,
360
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={ \"ok\": true, \"action\": \"{{ $json.aiAction }}\", \"customerId\": {{ $json.customerId }} }",
"options": {}
},
"id": "ok",
"name": "Respond 200 OK",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
5820,
360
]
}
],
"connections": {
"Evolution Webhook": {
"main": [
[
{
"node": "Verify Signature",
"type": "main",
"index": 0
}
]
]
},
"Verify Signature": {
"main": [
[
{
"node": "Verified?",
"type": "main",
"index": 0
}
]
]
},
"Verified?": {
"main": [
[
{
"node": "Personal Incoming Msg?",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond 401",
"type": "main",
"index": 0
}
]
]
},
"Personal Incoming Msg?": {
"main": [
[
{
"node": "Detect Message Type",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Skip",
"type": "main",
"index": 0
}
]
]
},
"Detect Message Type": {
"main": [
[
{
"node": "By Message Type",
"type": "main",
"index": 0
}
]
]
},
"By Message Type": {
"main": [
[
{
"node": "Text passthrough",
"type": "main",
"index": 0
}
],
[
{
"node": "Evolution: Get Audio",
"type": "main",
"index": 0
}
],
[
{
"node": "Evolution: Get Image",
"type": "main",
"index": 0
}
],
[
{
"node": "Other passthrough",
"type": "main",
"index": 0
}
]
]
},
"Evolution: Get Audio": {
"main": [
[
{
"node": "Whisper STT",
"type": "main",
"index": 0
}
]
]
},
"Whisper STT": {
"main": [
[
{
"node": "Audio \u2192 Text",
"type": "main",
"index": 0
}
]
]
},
"Evolution: Get Image": {
"main": [
[
{
"node": "GPT-4o Vision",
"type": "main",
"index": 0
}
]
]
},
"GPT-4o Vision": {
"main": [
[
{
"node": "Image \u2192 Caption",
"type": "main",
"index": 0
}
]
]
},
"Text passthrough": {
"main": [
[
{
"node": "Merge: Normalized",
"type": "main",
"index": 0
}
]
]
},
"Audio \u2192 Text": {
"main": [
[
{
"node": "Merge: Normalized",
"type": "main",
"index": 1
}
]
]
},
"Image \u2192 Caption": {
"main": [
[
{
"node": "Merge: Normalized",
"type": "main",
"index": 2
}
]
]
},
"Other passthrough": {
"main": [
[
{
"node": "Merge: Normalized",
"type": "main",
"index": 3
}
]
]
},
"Merge: Normalized": {
"main": [
[
{
"node": "Buffer: Debounce 6s",
"type": "main",
"index": 0
}
]
]
},
"Buffer: Debounce 6s": {
"main": [
[
{
"node": "Won debounce?",
"type": "main",
"index": 0
}
]
]
},
"Won debounce?": {
"main": [
[
{
"node": "Apply Buffered Text",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Skip Debounce",
"type": "main",
"index": 0
}
]
]
},
"Apply Buffered Text": {
"main": [
[
{
"node": "AlfaCRM: Auth (cached)",
"type": "main",
"index": 0
}
]
]
},
"AlfaCRM: Auth (cached)": {
"main": [
[
{
"node": "Carry Token + Ctx",
"type": "main",
"index": 0
}
]
]
},
"Carry Token + Ctx": {
"main": [
[
{
"node": "AlfaCRM: Find Customer",
"type": "main",
"index": 0
}
]
]
},
"AlfaCRM: Find Customer": {
"main": [
[
{
"node": "Customer Exists?",
"type": "main",
"index": 0
}
]
]
},
"Customer Exists?": {
"main": [
[
{
"node": "Merge: Customer Ctx",
"type": "main",
"index": 0
}
],
[
{
"node": "AlfaCRM: Create Customer",
"type": "main",
"index": 0
}
]
]
},
"AlfaCRM: Create Customer": {
"main": [
[
{
"node": "Merge: Customer Ctx",
"type": "main",
"index": 1
}
]
]
},
"Merge: Customer Ctx": {
"main": [
[
{
"node": "Build Lead Context",
"type": "main",
"index": 0
}
]
]
},
"Build Lead Context": {
"main": [
[
{
"node": "AI: Lead Qualifier",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI: Lead Qualifier",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Postgres Chat Memory": {
"ai_memory": [
[
{
"node": "AI: Lead Qualifier",
"type": "ai_memory",
"index": 0
}
]
]
},
"AI: Lead Qualifier": {
"main": [
[
{
"node": "Parse AI Output",
"type": "main",
"index": 0
}
]
]
},
"Parse AI Output": {
"main": [
[
{
"node": "Route by Action",
"type": "main",
"index": 0
}
]
]
},
"Route by Action": {
"main": [
[
{
"node": "Hot Lead \u2192 Telegram",
"type": "main",
"index": 0
},
{
"node": "AlfaCRM: Update Stage",
"type": "main",
"index": 0
}
],
[
{
"node": "AlfaCRM: Update Stage",
"type": "main",
"index": 0
}
],
[
{
"node": "Evolution: Send Reply",
"type": "main",
"index": 0
}
]
]
},
"Hot Lead \u2192 Telegram": {
"main": [
[]
]
},
"AlfaCRM: Update Stage": {
"main": [
[
{
"node": "Evolution: Send Reply",
"type": "main",
"index": 0
}
]
]
},
"Evolution: Send Reply": {
"main": [
[
{
"node": "Respond 200 OK",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"id": "ecab503c5ec7a4e6",
"versionId": "66666666-6666-6666-6666-666666666666",
"tags": []
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
WhatsApp → AlfaCRM Sales Funnel (Main, v2 Advanced). Uses httpRequest, openAi, agent, lmChatOpenAi. Webhook trigger; 43 nodes.
Source: https://github.com/klimichtg/n8n-whatsapp-alfacrm-funnel/blob/ff16cc6c73f32deab070fe8e206196206b57fecb/workflows/01_main_funnel.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.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.
Remi 1.1. Uses lmChatOpenAi, memoryPostgresChat, openAi, postgres. Webhook trigger; 89 nodes.
'Elena AI' is a powerful n8n workflow that transforms your automation platform into a full-fledged, multi-agent AI hub. 🤖✨ By combining Redis state management with specialized “tool” sub-workflows, yo
Delivery. Uses memoryPostgresChat, lmChatOpenAi, toolCalculator, redis. Webhook trigger; 37 nodes.
My workflow 6. Uses agent, lmChatOpenAi, openAi, httpRequest. Webhook trigger; 22 nodes.