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": "FullProject",
"nodes": [
{
"parameters": {
"jsCode": "/**\n * Logic Layer for WISP ERP - V2 (Updated with Session Lock)\n * \u062a\u0646\u0642\u064a\u0629 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0648\u062f\u0639\u0645 \u062d\u0642\u0648\u0644 \u0627\u0644\u0645\u064a\u0643\u0631\u0648\u062a\u0643 \u0648\u0636\u0645\u0627\u0646 \u062b\u0628\u0627\u062a \u0627\u0644\u0623\u0643\u0634\u0646 \u0623\u062b\u0646\u0627\u0621 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631\n */\n\n// 1. \u0627\u0633\u062a\u062e\u0631\u0627\u062c \u0631\u062f \u0627\u0644\u0640 AI \u0648\u0645\u0639\u0627\u0644\u062c\u062a\u0647\nconst aiRaw = (items[0].json.output || \"\").toString().trim(); \nlet aiParsed = { schema: null, action: null, params: {} };\n\ntry {\n const jsonMatch = aiRaw.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n aiParsed = JSON.parse(jsonMatch[0]);\n }\n} catch (e) {\n console.log(\"AI returned plain text or invalid JSON\");\n}\n\n// 2. \u062c\u0644\u0628 \u0628\u064a\u0627\u0646\u0627\u062a \u0627\u0644\u062c\u0644\u0633\u0629 (Session) \u0645\u0646 \u0646\u0648\u062f \u0642\u0627\u0639\u062f\u0629 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a\nconst sessionNode = $node[\"Get Session Data\"]?.json || {};\nconst sessionData = sessionNode.data || {};\nconst currentStep = sessionNode.step || 'idle';\n\n// 3. \u062f\u0627\u0644\u0629 \u062a\u0646\u0638\u064a\u0641 \u0627\u0644\u0646\u0635\u0648\u0635\nconst clean = (val) => (val ? val.toString().replace(/[\"']/g, \"\").trim() : null);\n\n// 4. \u0628\u0646\u0627\u0621 \u0627\u0644\u0640 Params \u0648\u062f\u0645\u062c \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0627\u0644\u062c\u062f\u064a\u062f\u0629\nlet finalParams = {\n ...sessionData, // \u0628\u064a\u0627\u0646\u0627\u062a \u0627\u0644\u062c\u0644\u0633\u0629 \u0627\u0644\u0642\u062f\u064a\u0645\u0629\n ...(aiParsed.params || {}), // \u0628\u064a\u0627\u0646\u0627\u062a \u0627\u0644\u0640 AI \u0627\u0644\u062c\u062f\u064a\u062f\u0629\n step: currentStep // \u0627\u0644\u062e\u0637\u0648\u0629 \u0627\u0644\u062d\u0627\u0644\u064a\u0629 \u0645\u0646 \u0627\u0644\u0633\u064a\u0634\u064a\u0646\n};\n\n// --- \u0645\u0639\u0627\u0644\u062c\u0629 \u0627\u0644\u062d\u0642\u0648\u0644 ---\nif (finalParams.password) finalParams.password = clean(finalParams.password);\nif (finalParams.mikrotik_username) finalParams.mikrotik_username = clean(finalParams.mikrotik_username);\n\nif (aiParsed.params?.query) {\n finalParams.query = clean(aiParsed.params.query);\n}\n\n// 5. \u0627\u0644\u0633\u0637\u0631 \u0627\u0644\u062d\u0627\u0633\u0645: \u062a\u062d\u062f\u064a\u062f \u0627\u0644\u0633\u0643\u064a\u0645\u0627 \u0648\u0627\u0644\u0623\u0643\u0634\u0646\n// \u0625\u0630\u0627 \u0643\u0646\u0627 \u0641\u064a \u062d\u0627\u0644\u0629 \u0627\u0646\u062a\u0638\u0627\u0631 (\u0627\u062e\u062a\u064a\u0627\u0631 \u0623\u0648 \u062a\u0623\u0643\u064a\u062f)\u060c \u0646\u0623\u062e\u0630 \u0627\u0644\u0642\u064a\u0645 \u0645\u0646 \u0627\u0644\u062c\u0644\u0633\u0629 \u062d\u0635\u0631\u0627\u064b \u0644\u0645\u0646\u0639 \u062a\u0634\u062a\u062a \u0627\u0644\u0640 AI\nconst isLockedStep = ['wait_choice', 'wait_confirmation'].includes(currentStep);\n\nconst finalPayload = {\n staff_lid: $('Webhook').first().json.body.payload.from, \n schema: isLockedStep ? (sessionNode.target_schema || \"clients\") : (aiParsed.schema || \"clients\"),\n action: isLockedStep ? (sessionNode.action || \"edit_plan\") : (aiParsed.action || \"info\"), \n step: currentStep,\n user_input: clean($('Webhook').first().json.body.payload.body), // \u0627\u0644\u0646\u0635 \u0627\u0644\u0635\u0631\u064a\u062d \u0627\u0644\u0644\u064a \u0628\u0639\u062a\u0647 \u0627\u0644\u0645\u0648\u0638\u0641 (\u0627\u0644\u0631\u0642\u0645 \u0623\u0648 \u0646\u0639\u0645/\u0644\u0627)\n params: finalParams\n};\n\n// \u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0642\u064a\u0645 \u063a\u064a\u0631 \u0627\u0644\u0645\u0639\u0631\u0641\u0629 \u0648\u0636\u0645\u0627\u0646 \u0633\u0644\u0627\u0645\u0629 \u0627\u0644\u0640 JSON\nreturn JSON.parse(JSON.stringify(finalPayload));"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-464,
48
],
"id": "09e16b00-3b5e-44f7-bf4c-20295025fd8d",
"name": "Code in JavaScript"
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT step, data, action, target_schema \nFROM staff.sessions \nWHERE whatsapp_lid = '{{ $('Webhook').item.json.body.payload.from }}';",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
-2416,
48
],
"id": "c8d766e6-900b-40f3-8ecf-2c726c952bd9",
"name": "Get Session Data",
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"command": "={{ $json.global_router.ssh_cmd }}",
"cwd": "="
},
"type": "n8n-nodes-base.ssh",
"typeVersion": 1,
"position": [
208,
-48
],
"id": "9704f89c-8ac1-46d5-a0d1-d7036eff38fe",
"name": "Execute a command",
"alwaysOutputData": true,
"credentials": {
"sshPassword": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.global_router.ssh_cmd }}",
"rightValue": "secret",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"id": "e7aa2beb-f83c-46c9-8153-43e0aaa51299"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"id": "7b290623-1d89-4257-ae0b-e358a8ba0bc3",
"leftValue": "=$json.db_response.ssh_cmd",
"rightValue": ".+",
"operator": {
"type": "string",
"operation": "regex"
}
}
],
"combinator": "and"
}
}
]
},
"looseTypeValidation": true,
"options": {
"allMatchingOutputs": false
}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
-16,
48
],
"id": "dae6927b-fdc4-4eea-920f-1634f321c41c",
"name": "Switch"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "e7851051-a9fa-4cdd-80c7-0de82d436fbd",
"name": "response",
"value": "={{ $json.global_router.human_msg }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
208,
144
],
"id": "9d7322e2-5803-4b88-a7e1-3362a3d821cc",
"name": "HumanResponse1"
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT public.global_router($1::text::jsonb);",
"options": {
"queryReplacement": "={{ JSON.stringify($json) }}"
}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
-240,
48
],
"id": "097db9e5-8aa2-4c55-8de3-e7a7e1396a3b",
"name": "GlobalRouter",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"httpMethod": "POST",
"path": "daf1b89b-4f35-495c-b36e-c9ca630cdebf",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-3088,
48
],
"id": "ae57020c-5cfb-4e9d-b93f-cc773c215cae",
"name": "Webhook"
},
{
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:3000/api/sendText",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "chatId",
"value": "={{ $node[\"Webhook\"].json[\"body\"][\"payload\"][\"from\"] }}"
},
{
"name": "text",
"value": "={{ $json.response }}"
},
{
"name": "session",
"value": "default"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
432,
144
],
"id": "d273139e-2031-46e3-bd3e-2bf776a7f4ba",
"name": "HTTP Request",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"promptType": "define",
"text": "={{ $('Webhook').item.json.body.payload.body }}",
"options": {
"systemMessage": "={{ $json.system_prompt }}"
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
-1392,
48
],
"id": "5e660622-acd9-4b11-8e47-f8199d157267",
"name": "AI Agent1",
"alwaysOutputData": true,
"onError": "continueRegularOutput"
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT id, f_name, role_id \nFROM staff.list \nWHERE whatsapp_lid = '{{ $json.body.payload.from }}' \nAND status = 'active';",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
-2864,
48
],
"id": "135b5b52-e7a0-4695-96a8-9feb0749b582",
"name": "Execute a SQL query",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "4761d516-8b67-4557-b836-ca31adb86bd2",
"leftValue": "={{ $json.f_name }}",
"rightValue": 0,
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "or"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-2640,
48
],
"id": "17424f3d-b905-499c-a6c0-8b06bae142c4",
"name": "If"
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH raw_data AS (\n -- \u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0646\u0635 \u0625\u0644\u0649 \u0645\u0635\u0641\u0648\u0641\u0629 \u0628\u0646\u0627\u0621\u064b \u0639\u0644\u0649 \u0627\u0644\u0633\u0637\u0631 \u0627\u0644\u062c\u062f\u064a\u062f \u0648\u062a\u0641\u0643\u064a\u0643\u0647\u0627\n SELECT trim(cmd) as single_cmd\n FROM unnest(string_to_array('{{ $('GlobalRouter').item.json.global_router.ssh_cmd }}', E'\\n')) AS cmd\n WHERE trim(cmd) <> ''\n),\ninsertion AS (\n INSERT INTO mikrotik.commands_queue (client_id, command, action_type, status)\n SELECT \n -- \u0645\u0639\u0627\u0644\u062c\u0629 \u0627\u0644\u0640 client_id \u0644\u0636\u0645\u0627\u0646 \u0639\u062f\u0645 \u0648\u062c\u0648\u062f \u0642\u064a\u0645 \u0646\u0635\u064a\u0629 \u063a\u064a\u0631 \u0635\u062d\u064a\u062d\u0629\n NULLIF('{{ $('Get Session Data').item.json.data.client_id }}', 'undefined')::int, \n rd.single_cmd, \n -- \u062a\u0635\u062d\u064a\u062d \u0627\u0644\u0642\u0648\u0633 \u0627\u0644\u0641\u0627\u0631\u063a \u0648\u0631\u0628\u0637\u0647 \u0628\u0627\u0644\u0623\u0643\u0634\u0646 \u0627\u0644\u0642\u0627\u062f\u0645 \u0645\u0646 \u0627\u0644\u0631\u0627\u0648\u062a\u0631\n NULLIF('{{ $('Code in JavaScript').item.json.action}}', 'undefined'), \n 'pending'\n FROM raw_data rd\n WHERE NOT EXISTS (\n -- \u0627\u0644\u062a\u0623\u0643\u062f \u0645\u0646 \u0639\u062f\u0645 \u062a\u0643\u0631\u0627\u0631 \u0646\u0641\u0633 \u0627\u0644\u0623\u0645\u0631 \u0644\u0646\u0641\u0633 \u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645 \u0641\u064a \u062d\u0627\u0644\u0629 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631\n SELECT 1 FROM mikrotik.commands_queue cq \n WHERE cq.command = rd.single_cmd \n AND cq.client_id = NULLIF('{{ $('Get Session Data').item.json.data.client_id }}', 'undefined')::int\n AND cq.status = 'pending'\n )\n RETURNING id\n)\nSELECT \n CASE \n WHEN (SELECT count(*) FROM insertion) > 0 THEN 'inserted_' || (SELECT count(*) FROM insertion)::text\n ELSE 'exists_or_nothing_new'\n END AS result_status;",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
656,
48
],
"id": "b6708a45-740d-4212-9d58-b99395c6847f",
"name": "Execute a SQL query1",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "9aa4699a-a257-4c6c-83fa-38b9e4721c0c",
"name": "db_response.human_msg",
"value": "={{ $('GlobalRouter').item.json.global_router.human_msg }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
656,
-144
],
"id": "63ecc37a-6a9c-4ddd-a446-7ea1d46e5638",
"name": "HumanResponse2"
},
{
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:3000/api/sendText",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "chatId",
"value": "={{ $node[\"Webhook\"].json[\"body\"][\"payload\"][\"from\"] }}"
},
{
"name": "text",
"value": "={{ $json.db_response.human_msg }}"
},
{
"name": "session",
"value": "default"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
880,
-144
],
"id": "f591396f-9b65-4547-b395-478735eaeed3",
"name": "HTTP Request2",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "96e0b9d6-38cc-45b7-b161-1d35dffb792d",
"leftValue": "={{ $json.code }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
432,
-48
],
"id": "c9095679-f82c-4a85-8fa1-0b6c063ecb1c",
"name": "If1"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "fd7c4735-31fc-4319-a0c7-bb7262b562e6",
"name": "text",
"value": "={{ $json.result_status === 'inserted_1' \n ? \"\u26a0\ufe0f \u062a\u0639\u0630\u0631 \u0627\u0644\u0627\u062a\u0635\u0627\u0644 \u0628\u0627\u0644\u0645\u064a\u0643\u0631\u0648\u062a\u064a\u0643 \u062d\u0627\u0644\u064a\u0627\u064b. \u062a\u0645 \u062a\u0633\u062c\u064a\u0644 \u0627\u0644\u0639\u0645\u0644\u064a\u0629 \u0628\u0646\u062c\u0627\u062d \u0641\u064a \u0637\u0627\u0628\u0648\u0631 \u0627\u0644\u062a\u0646\u0641\u064a\u0630 \u0627\u0644\u062a\u0644\u0642\u0627\u0626\u064a \u0648\u0633\u064a\u062a\u0645 \u062a\u0646\u0641\u064a\u0630\u0647\u0627 \u0641\u0648\u0631 \u0639\u0648\u062f\u0629 \u0627\u0644\u062e\u062f\u0645\u0629. \u2705\" \n : \"\u2139\ufe0f \u0645\u0644\u0627\u062d\u0638\u0629: \u0647\u0630\u0627 \u0627\u0644\u0637\u0644\u0628 \u0645\u0648\u062c\u0648\u062f \u0645\u0633\u0628\u0642\u0627\u064b \u0641\u064a \u0637\u0627\u0628\u0648\u0631 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631\u060c \u0644\u0627 \u062f\u0627\u0639\u064a \u0644\u0625\u0639\u0627\u062f\u0629 \u0625\u0631\u0633\u0627\u0644\u0647. \u0633\u064a\u062a\u0645 \u062a\u0646\u0641\u064a\u0630\u0647 \u062a\u0644\u0642\u0627\u0626\u064a\u0627\u064b \u0639\u0646\u062f \u0639\u0648\u062f\u0629 \u0627\u0644\u0627\u062a\u0635\u0627\u0644. \u23f3\" \n}}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
880,
48
],
"id": "323bbd3a-3a7a-4eba-b7a4-31e4ec4f2c51",
"name": "HumanResponse"
},
{
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:3000/api/sendText",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "chatId",
"value": "={{ $node[\"Webhook\"].json[\"body\"][\"payload\"][\"from\"] }}"
},
{
"name": "text",
"value": "={{ $json.text }}"
},
{
"name": "session",
"value": "default"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1104,
48
],
"id": "c80a9041-4246-425f-9cfd-3053df1c21a9",
"name": "HTTP Request3",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"promptType": "define",
"text": "={{ $('Webhook').item.json.body.payload.body }}",
"options": {
"systemMessage": "Role: WISP Router Classifier.\nTask: Respond with ONE word only (bills, clients, staff).\n\nLogic Rules:\nbills: [Actions: pay, report, clear_cash] - Keywords: (\u0642\u0628\u0636\u060c \u062f\u0641\u0639\u060c \u062a\u062c\u062f\u064a\u062f\u060c \u062c\u0631\u062f\u060c \u063a\u0644\u0629\u060c \u0634\u062d\u0646\u060c \u0631\u0635\u064a\u062f\u060c \u062f\u064a\u0648\u0646\u060c \u0633\u062f\u062f\u060c \u0645\u0627\u0644\u064a\u0629\u060c \u062a\u0635\u0641\u064a\u060c \u0641\u0635\u0644).\n\nclients: [Actions: add, edit, info] - Keywords: (\u0645\u0634\u062a\u0631\u0643\u060c \u062a\u0641\u0639\u064a\u0644\u060c \u0646\u0627\u0646\u0648\u060c \u0631\u0627\u0648\u062a\u0631\u060c \u064a\u0648\u0632\u0631\u060c \u0628\u0627\u0642\u0629\u060c \u0633\u0631\u0639\u0629\u060c \u0636\u064a\u0641\u060c \u0645\u064a\u0646 \u0647\u0630\u0627\u060c \u0641\u062d\u0635\u060c \u0645\u0639\u0644\u0648\u0645\u0627\u062a).\n\nstaff: [Actions: logs] - Keywords: (\u0633\u062c\u0644\u060c \u0644\u0648\u063a\u0627\u062a\u060c \u0639\u0645\u0644\u064a\u0627\u062a\u060c \u0634\u0648 \u0639\u0645\u0644\u060c \u062a\u062d\u0631\u0643\u0627\u062a\u060c \u0645\u0631\u0627\u0642\u0628\u0629\u060c \u062d\u0638\u0631\u060c \u0641\u0643 \u062d\u0638\u0631).\n\nConstraint: Output ONLY the schema name. Default to 'clients' if unclear."
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
-2192,
48
],
"id": "c4ad80e8-b6c2-4bac-a7cb-6f678b076eb1",
"name": "AI Agent"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "ace38dc2-4144-45f4-8b3f-b732b87d0b0f",
"name": "system_prompt",
"value": "={{ $json.system_prompt }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-1616,
48
],
"id": "f36f154f-f19e-4cee-8c4e-39bf4ba08641",
"name": "Edit Fields"
},
{
"parameters": {
"model": "qwen2.5:7b-instruct-q4_K_M",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
"typeVersion": 1,
"position": [
-2240,
320
],
"id": "2a0c6e37-be35-46da-bff6-98cf6470c3df",
"name": "Ollama Chat Model",
"credentials": {
"ollamaApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"model": "llama3.1:latest",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
"typeVersion": 1,
"position": [
-976,
272
],
"id": "941f0bd6-d223-43a2-b9cd-4ea781ac37ac",
"name": "Ollama Chat Model1",
"credentials": {
"ollamaApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "042aaa82-4bf4-4fb8-833d-877923e2e964",
"leftValue": "={{ $json.output }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-1040,
48
],
"id": "3e6b7c9d-3b06-4f78-887c-04af76cb9618",
"name": "If2"
},
{
"parameters": {
"promptType": "define",
"text": "={{ $('Webhook').item.json.body.payload.body }}",
"options": {
"systemMessage": "={{ $('System Prompt').item.json.system_prompt }}"
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
-816,
-80
],
"id": "b325aebf-5d8a-470b-a7c3-6cad259a9a71",
"name": "AI Agent2"
},
{
"parameters": {
"model": "llama-3.3-70b-versatile",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatGroq",
"typeVersion": 1,
"position": [
-1536,
320
],
"id": "c235cfa6-9192-4e0f-8512-710ea7dd0745",
"name": "Groq Chat Model",
"credentials": {
"groqApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT system_prompt FROM staff.fetch_workflow_context('{{ $node[\"AI Agent\"].json[\"output\"] }}');",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
-1840,
48
],
"id": "f15581f3-c5a6-49c7-80f9-b55cf1b96430",
"name": "System Prompt",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"content": "### \ud83d\udee1\ufe0f Staff Verification Gateway\nBefore starting the workflow, a conditional IF node queries the database to check if the sender's whatsapp lid is registered in the staff table. If the number is found, the workflow continues to the AI agent; otherwise, the request is dropped immediately to protect the system.\n",
"height": 304,
"width": 368,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-3072,
256
],
"id": "3f7c9e95-4991-433b-b3f4-c0cc4a931873",
"name": "Sticky Note"
},
{
"parameters": {
"content": "### \ud83c\udfaf Dynamic Prompt Retrieval\nA lightweight routing agent detects the targeted database schema based on the staff's message, then dynamically fetches only its specific, small prompt from the database. This significantly cuts down token consumption and eliminates AI hallucinations.",
"height": 240,
"width": 512,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2240,
-256
],
"id": "4787b685-5c50-4dc7-9dfa-b468736ff547",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "### \ud83e\udd16 Intent Parser (Groq LLM)\nThe primary AI agent receives the dynamic prompt alongside the staff's message to analyze the request. It outputs a clean, single-purpose JSON payload containing only the extracted arguments, with absolutely no conversational filler text.\n\n\n\n",
"height": 240,
"width": 528,
"color": 7
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1568,
-288
],
"id": "24ca4c82-c165-41f5-8824-52aeed642570",
"name": "Sticky Note2"
},
{
"parameters": {
"content": "### \ud83d\udd04 Groq Failover Gate (If2)\nA critical conditional node monitors the execution status of the primary AI agent. If the external Groq API fails due to unexpected errors or hitting token limits, it instantly routes the same payload to the local offline Ollama model to ensure continuous uptime.",
"height": 192,
"width": 448,
"color": "#610000"
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-896,
-320
],
"id": "ba858be9-fe70-4ca9-b67b-bafe6bab19e0",
"name": "Sticky Note3"
},
{
"parameters": {
"content": "### \ud83d\udddc\ufe0f JSON Parameter Structuring (JavaScript Node)\nThis script captures the raw output from either AI engine and structures it neatly into the exact data layout expected by the database. It normalizes variable keys and wraps them perfectly to feed into the core `public.global_router()`.",
"height": 240,
"width": 432,
"color": 6
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-624,
208
],
"id": "c41caf05-370b-4127-9cbd-a2aa0d2ff417",
"name": "Sticky Note4"
},
{
"parameters": {
"content": "### \ud83c\udf9b\ufe0f Execution Path Switch\nThis conditional router evaluates the query results returned from the database. If the payload contains a generated network string (`ssh_cmd`), it branches upward to fire terminal updates at the MikroTik gateway; otherwise, for data updates or standard lookups, it branches downward.\n\n",
"height": 272,
"width": 336,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-208,
-272
],
"id": "fcc015fa-2193-40a2-88b4-52aacc990346",
"name": "Sticky Note5"
},
{
"parameters": {
"content": "### \ud83d\udd0c MikroTik CLI Executor (Execute a Command)\nWhen the switch detects an active configuration string, this node connects directly to the Mikrotik router via SSH. It executes the dynamic command loop on the live hardware terminal to immediately reshape user profiles or disconnect active network accounts.",
"height": 368,
"width": 256
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
208,
-480
],
"id": "17c5adbf-5853-425c-a7b5-e2e6718582b8",
"name": "Sticky Note6"
},
{
"parameters": {
"content": "### \ud83c\udf10 MikroTik Gateway Monitor (If1)\nThis conditional node checks if the live MikroTik router connection is fully active. If the gateway is online, it routes to a success message confirming the hardware configuration was updated; if offline, it redirects the payload to the fallback queue database.\n\n",
"height": 224,
"width": 432,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
656,
-432
],
"id": "9d83d33a-2a59-4fed-9069-d27819645683",
"name": "Sticky Note7"
},
{
"parameters": {
"content": "### \ud83d\udce5 Offline Command Queue (Execute a SQL Query1)\nWhen the router is offline, this node captures the dynamic configuration string and inserts it directly into the database queue table. This ensures no network updates are lost, leaving them ready to sync automatically the moment the gateway breathes back to life.",
"height": 240,
"width": 416,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
768,
224
],
"id": "0c84e14c-9ace-401b-be92-2db18ec24f34",
"name": "Sticky Note8"
}
],
"connections": {
"Code in JavaScript": {
"main": [
[
{
"node": "GlobalRouter",
"type": "main",
"index": 0
}
]
]
},
"Get Session Data": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Execute a command",
"type": "main",
"index": 0
}
],
[
{
"node": "HumanResponse1",
"type": "main",
"index": 0
}
]
]
},
"GlobalRouter": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Execute a SQL query",
"type": "main",
"index": 0
}
]
]
},
"HumanResponse1": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"AI Agent1": {
"main": [
[
{
"node": "If2",
"type": "main",
"index": 0
}
]
]
},
"Execute a SQL query": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"If": {
"main": [
[
{
"node": "Get Session Data",
"type": "main",
"index": 0
}
]
]
},
"Execute a command": {
"main": [
[
{
"node": "If1",
"type": "main",
"index": 0
}
]
]
},
"HumanResponse2": {
"main": [
[
{
"node": "HTTP Request2",
"type": "main",
"index": 0
}
]
]
},
"If1": {
"main": [
[
{
"node": "HumanResponse2",
"type": "main",
"index": 0
}
],
[
{
"node": "Execute a SQL query1",
"type": "main",
"index": 0
}
]
]
},
"HumanResponse": {
"main": [
[
{
"node": "HTTP Request3",
"type": "main",
"index": 0
}
]
]
},
"Execute a SQL query1": {
"main": [
[
{
"node": "HumanResponse",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "System Prompt",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "AI Agent1",
"type": "main",
"index": 0
}
]
]
},
"Ollama Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Ollama Chat Model1": {
"ai_languageModel": [
[
{
"node": "AI Agent2",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"If2": {
"main": [
[
{
"node": "AI Agent2",
"type": "main",
"index": 0
}
],
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"AI Agent2": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Groq Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent1",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"System Prompt": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "c2321c5e-cfb8-47e9-9724-efd7d2ed120c",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "Vz66aL6M1uESx17z",
"tags": []
}
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.
groqApihttpHeaderAuthollamaApipostgressshPassword
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
FullProject. Uses postgres, ssh, httpRequest, agent. Webhook trigger; 34 nodes.
Source: https://github.com/kamal966/ChatBased-Ai-Management-System/blob/8cbde3fff7201be2aa74c5d97e2d41215d54c617/n8n/FullProject.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 implements a privacy-preserving AI document processing pipeline that detects, masks, and securely manages Personally Identifiable Information (PII) before any AI processing occurs.
Asistente Personal WhatsApp. Uses postgres, agent, lmChatGroq, outputParserStructured. Webhook trigger; 18 nodes.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.
Remi 1.1. Uses lmChatOpenAi, memoryPostgresChat, openAi, postgres. Webhook trigger; 89 nodes.
WhatsApp AI Agent - Template. Uses redis, httpRequest, postgres, agent. Webhook trigger; 89 nodes.