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": "VocTest \u2014 Chat Conversacional",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "voctest/chat-handler",
"responseMode": "responseNode",
"options": {}
},
"id": "8fc49d27-832b-462c-beea-c76f1e010558",
"name": "[Webhook] Chat entrante",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-1520,
528
]
},
{
"parameters": {
"operation": "get",
"propertyName": "value",
"key": "={{ 'voc:session:' + $json.body.sessionId }}",
"options": {}
},
"id": "1800e18c-c31b-4866-9e3b-5146abc38a3d",
"name": "[Redis] GET Sesi\u00f3n",
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
-1296,
528
],
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"typeValidation": "strict"
},
"conditions": [
{
"id": "session-exists",
"leftValue": "={{ $json.value }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "f624851e-5dad-4bf5-a579-dc8c92a3beeb",
"name": "[IF] Sesi\u00f3n existe",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-1072,
528
]
},
{
"parameters": {
"jsCode": "try {\n const body = $('[Webhook] Chat entrante').item.json.body;\n const rawValue = $input.item.json.value;\n let session = typeof rawValue === 'string' ? JSON.parse(rawValue) : rawValue;\n session.lastActivity = new Date().toISOString();\n return [{ json: { session, userMessage: body.message } }];\n} catch(e) {\n throw new Error('[Parse sesi\u00f3n existente] ' + e.message);\n}"
},
"id": "72f1c896-569e-4b92-8e2f-5ca7315d3edd",
"name": "[Code] Parse sesi\u00f3n existente",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-848,
432
]
},
{
"parameters": {
"jsCode": "const body = $('[Webhook] Chat entrante').item.json.body;\nconst session = {\n sessionId: body.sessionId || ('sess_' + Date.now() + '_' + Math.random().toString(36).substr(2,9)),\n userId: body.userId || null,\n tenantId: body.tenantId || null,\n plan: body.plan || 'b2c',\n email: body.email || null,\n userName: body.userName || 'Estudiante',\n phase: 'WELCOME',\n status: 'chatting',\n questionCount: 0,\n riasecScores: { R: 0, I: 0, A: 0, S: 0, E: 0, C: 0 },\n topicsAsked: [],\n history: [],\n createdAt: new Date().toISOString(),\n lastActivity: new Date().toISOString()\n};\nreturn [{ json: {\n session,\n userMessage: body.message || 'Hola, quiero hacer el test vocacional'\n} }];"
},
"id": "724b055f-38ec-4016-a36a-655a4900afd1",
"name": "[Code] Iniciar nueva sesi\u00f3n",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-624,
816
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "awaiting-email-condition",
"leftValue": "={{ $json.session.status }}",
"rightValue": "awaiting_email",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "4874d02d-468a-4d57-b988-18ef7dafba03",
"name": "[IF] Esperando email",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-624,
432
]
},
{
"parameters": {
"jsCode": "const { session, userMessage } = $input.item.json;\nconst emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/;\nconst emailValido = emailRegex.test(userMessage.trim());\n\nif (!emailValido) {\n session.history.push({ role: 'user', content: userMessage });\n session.history.push({ role: 'assistant', content: '\u00a1Ups! Ese no parece un email v\u00e1lido. \u00bfPod\u00e9s escribirlo de nuevo? Por ejemplo: nombre@correo.com' });\n session.lastActivity = new Date().toISOString();\n return [{ json: { session, responseMessage: '\u00a1Ups! Ese no parece un email v\u00e1lido. \u00bfPod\u00e9s escribirlo de nuevo? Por ejemplo: nombre@correo.com', testCompleto: false, questionCount: session.questionCount, phase: 'COMPLETE', emailValido: false } }];\n}\n\nconst email = userMessage.trim().toLowerCase();\nsession.email = email;\nsession.status = 'complete';\nsession.history.push({ role: 'user', content: userMessage });\nsession.history.push({ role: 'assistant', content: '\u00a1Perfecto! Tu informe vocacional va a ser enviado a ' + email + '. En unos segundos ver\u00e1s tu perfil completo aqu\u00ed. \ud83c\udf89' });\nsession.lastActivity = new Date().toISOString();\n\nreturn [{ json: { session, responseMessage: '\u00a1Perfecto! Tu informe vocacional va a ser enviado a ' + email + '. En unos segundos ver\u00e1s tu perfil completo aqu\u00ed. \ud83c\udf89', testCompleto: true, questionCount: session.questionCount, phase: 'COMPLETE', emailValido: true } }];"
},
"id": "a6a61c41-9c86-457e-a2c9-39ab14e399b8",
"name": "[Code] Procesar email recibido",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-112,
384
]
},
{
"parameters": {
"jsCode": "const { session, userMessage } = $input.item.json;\nconst s = session.riasecScores;\nconst dimCounts = {};\nsession.topicsAsked.forEach(function(d) { dimCounts[d] = (dimCounts[d] || 0) + 1; });\nconst dims = ['R', 'I', 'A', 'S', 'E', 'C'];\nconst pendientes = dims.slice().sort(function(a, b) { return (dimCounts[a] || 0) - (dimCounts[b] || 0); }).slice(0, 3).join(', ');\nconst sorted = Object.entries(s).sort(function(a, b) { return b[1] - a[1]; });\nconst perfilEmergente = sorted.filter(function(x) { return x[1] > 0; }).map(function(x) { return x[0] + ':' + x[1]; }).join(' ') || 'sin datos a\u00fan';\n\nreturn [{ json: {\n session,\n userMessage,\n agentContext: JSON.stringify({\n sessionId: session.sessionId,\n userName: session.userName,\n phase: session.phase,\n status: session.status,\n questionCount: session.questionCount,\n riasecScores: s,\n perfilEmergente: perfilEmergente,\n dimensionesPendientes: pendientes,\n topicsAsked: Array.from(new Set(session.topicsAsked))\n })\n} }];"
},
"id": "cbe4cf8a-d6a2-4669-ad64-a0e85982f767",
"name": "[Code] Preparar contexto agente",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-400,
768
]
},
{
"parameters": {
"model": {
"__rl": true,
"value": "gpt-4o",
"mode": "list",
"cachedResultName": "GPT-4o"
},
"builtInTools": {},
"options": {}
},
"id": "29d37e02-2d34-4a41-a728-fa8ab8906064",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.3,
"position": [
-192,
944
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ 'voc:agent:' + $('[Code] Preparar contexto agente').item.json.session.sessionId }}",
"contextWindowLength": 20
},
"id": "d6edadec-b371-4a43-aa0a-b45c50306525",
"name": "Redis Chat Memory",
"type": "@n8n/n8n-nodes-langchain.memoryRedisChat",
"typeVersion": 1.5,
"position": [
-80,
944
],
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.userMessage }}",
"options": {
"systemMessage": "=Sos un psic\u00f3logo vocacional experto en Holland RIASEC. Conduc\u00eds entrevistas vocacionales conversacionales, c\u00e1lidas y profesionales en espa\u00f1ol rioplatense.\n\nCONTEXTO ACTUAL DE LA SESI\u00d3N:\n{{ $('[Code] Preparar contexto agente').item.json.agentContext }}\n\nDIMENSIONES RIASEC:\nR - Realista: manual, t\u00e9cnico, mec\u00e1nico, aire libre\nI - Investigador: ciencia, an\u00e1lisis, curiosidad intelectual\nA - Art\u00edstico: creatividad, dise\u00f1o, m\u00fasica, expresi\u00f3n\nS - Social: ayudar, ense\u00f1ar, empat\u00eda, trabajo con personas\nE - Emprendedor: liderazgo, negocios, influencia, ambici\u00f3n\nC - Convencional: orden, datos, administraci\u00f3n, precisi\u00f3n\n\nINSTRUCCIONES ESTRICTAS:\n1. Fase WELCOME: Salud\u00e1 con calidez. Present\u00e1 el test en 2 oraciones. Si no ten\u00e9s el nombre, pedilo.\n2. Fase EXPLORING: UNA sola pregunta por turno. Natural, conversacional. NUNCA mencion\u00e9s RIASEC ni nombres de dimensiones.\n3. Prioriz\u00e1 dimensiones con menos datos seg\u00fan dimensionesPendientes del contexto.\n4. Vari\u00e1: hobbies, materias, situaciones, referentes, trabajo ideal.\n5. Cuando questionCount llegue a 25: ped\u00ed el email con entusiasmo.\n6. M\u00e1ximo 100 palabras. Tono c\u00e1lido y emp\u00e1tico.\n\nCUANDO questionCount >= 25, respond\u00e9 con pedirEmail: true:\n{\"message\":\"\u00a1Excelente! Ya tengo toda la informaci\u00f3n para generar tu informe vocacional personalizado. \u00bfA qu\u00e9 correo electr\u00f3nico quer\u00e9s que te lo enviemos?\",\"dimensionEvaluada\":\"NONE\",\"pesoRespuesta\":0,\"nuevaFase\":\"COMPLETE\",\"testCompleto\":false,\"pedirEmail\":true}\n\nCASO NORMAL \u2014 RESPOND\u00c9 SOLO CON ESTE JSON PURO SIN MARKDOWN:\n{\"message\":\"tu respuesta\",\"dimensionEvaluada\":\"R|I|A|S|E|C|NONE\",\"pesoRespuesta\":1,\"nuevaFase\":\"WELCOME|EXPLORING|COMPLETE\",\"testCompleto\":false,\"pedirEmail\":false}"
}
},
"id": "60111b87-1469-47a3-bf1c-fba6cd4bd58f",
"name": "[AI Agent] Entrevistador RIASEC",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
-176,
768
]
},
{
"parameters": {
"jsCode": "try {\n const agentOutput = $input.item.json.output;\n const session = $('[Code] Preparar contexto agente').item.json.session;\n const userMessage = $('[Code] Preparar contexto agente').item.json.userMessage;\n\n let aiResponse;\n try {\n const raw = typeof agentOutput === 'string' ? agentOutput : JSON.stringify(agentOutput);\n const cleaned = raw.replace(/^```json?\\n?/, '').replace(/\\n?```$/, '').trim();\n aiResponse = JSON.parse(cleaned);\n } catch(e) {\n aiResponse = {\n message: typeof agentOutput === 'string' ? agentOutput : 'Continuemos con el test.',\n dimensionEvaluada: 'NONE',\n pesoRespuesta: 0,\n nuevaFase: session.phase,\n testCompleto: false,\n pedirEmail: false\n };\n }\n\n const dim = aiResponse.dimensionEvaluada;\n if (dim && dim !== 'NONE' && dim in session.riasecScores) {\n session.riasecScores[dim] += (aiResponse.pesoRespuesta || 1);\n session.topicsAsked.push(dim);\n session.questionCount += 1;\n }\n\n session.history.push({ role: 'user', content: userMessage });\n session.history.push({ role: 'assistant', content: aiResponse.message });\n if (session.history.length > 30) { session.history = session.history.slice(-30); }\n session.lastActivity = new Date().toISOString();\n\n if (session.phase === 'WELCOME' && session.history.length >= 2) { session.phase = 'EXPLORING'; }\n if (aiResponse.pedirEmail === true || session.questionCount >= 25) {\n session.status = 'awaiting_email';\n session.phase = 'COMPLETE';\n }\n\n return [{ json: { session, responseMessage: aiResponse.message, testCompleto: false, questionCount: session.questionCount, phase: session.phase, emailValido: false } }];\n} catch(e) {\n throw new Error('[Parse agente] ' + e.message);\n}"
},
"id": "ef464cec-5dc8-4ef0-9d5c-2d39f27345dc",
"name": "[Code] Parsear respuesta agente",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
176,
768
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "email-valid-condition",
"leftValue": "={{ $json.emailValido }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "b6655a28-63f1-402e-98ba-7684d2f29f42",
"name": "[IF] Email v\u00e1lido",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
176,
384
]
},
{
"parameters": {
"operation": "set",
"key": "={{ 'voc:session:' + $json.session.sessionId }}",
"value": "={{ JSON.stringify($json.session) }}",
"expire": true,
"ttl": 86400
},
"id": "a2924895-ba27-4184-a1e4-ae10f8b1ca9d",
"name": "[Redis] SET Sesi\u00f3n completa",
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
400,
336
],
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ 'voc:session:' + $json.session.sessionId }}",
"value": "={{ JSON.stringify($json.session) }}",
"expire": true,
"ttl": 7200
},
"id": "2c792fe5-328d-43eb-8c80-43183da767c4",
"name": "[Redis] SET Sesi\u00f3n continua",
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
400,
720
],
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ message: $json.responseMessage, phase: $json.phase, questionCount: $json.questionCount, sessionId: $json.session.sessionId, testCompleto: $json.testCompleto, processing: false }) }}",
"options": {}
},
"id": "b8e36c6b-7a32-4797-9eaf-83368011efd6",
"name": "[Respond] Chat continua",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
624,
720
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ message: $json.responseMessage, phase: 'COMPLETE', questionCount: $json.questionCount, sessionId: $json.session.sessionId, testCompleto: false, processing: true }) }}",
"options": {}
},
"id": "6b71661b-f2f1-4678-8c20-cab19f3d7aec",
"name": "[Respond] Test procesando",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
624,
240
]
},
{
"parameters": {
"method": "POST",
"url": "https://gmn8nwebhook.goodapps.space/webhook/voctest/analysis",
"sendBody": true,
"contentType": "json",
"body": "={{ JSON.stringify($json.session) }}",
"options": {
"timeout": 60000
}
},
"id": "26ffe2d6-e19c-4d06-b146-1a98c672383c",
"name": "[HTTP] Llamar an\u00e1lisis",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
624,
432
]
}
],
"connections": {
"[Webhook] Chat entrante": {
"main": [
[
{
"node": "[Redis] GET Sesi\u00f3n",
"type": "main",
"index": 0
}
]
]
},
"[Redis] GET Sesi\u00f3n": {
"main": [
[
{
"node": "[IF] Sesi\u00f3n existe",
"type": "main",
"index": 0
}
]
]
},
"[IF] Sesi\u00f3n existe": {
"main": [
[
{
"node": "[Code] Parse sesi\u00f3n existente",
"type": "main",
"index": 0
}
],
[
{
"node": "[Code] Iniciar nueva sesi\u00f3n",
"type": "main",
"index": 0
}
]
]
},
"[Code] Parse sesi\u00f3n existente": {
"main": [
[
{
"node": "[IF] Esperando email",
"type": "main",
"index": 0
}
]
]
},
"[Code] Iniciar nueva sesi\u00f3n": {
"main": [
[
{
"node": "[Code] Preparar contexto agente",
"type": "main",
"index": 0
}
]
]
},
"[IF] Esperando email": {
"main": [
[
{
"node": "[Code] Procesar email recibido",
"type": "main",
"index": 0
}
],
[
{
"node": "[Code] Preparar contexto agente",
"type": "main",
"index": 0
}
]
]
},
"[Code] Procesar email recibido": {
"main": [
[
{
"node": "[IF] Email v\u00e1lido",
"type": "main",
"index": 0
}
]
]
},
"[Code] Preparar contexto agente": {
"main": [
[
{
"node": "[AI Agent] Entrevistador RIASEC",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "[AI Agent] Entrevistador RIASEC",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Redis Chat Memory": {
"ai_memory": [
[
{
"node": "[AI Agent] Entrevistador RIASEC",
"type": "ai_memory",
"index": 0
}
]
]
},
"[AI Agent] Entrevistador RIASEC": {
"main": [
[
{
"node": "[Code] Parsear respuesta agente",
"type": "main",
"index": 0
}
]
]
},
"[Code] Parsear respuesta agente": {
"main": [
[
{
"node": "[Redis] SET Sesi\u00f3n continua",
"type": "main",
"index": 0
}
]
]
},
"[IF] Email v\u00e1lido": {
"main": [
[
{
"node": "[Redis] SET Sesi\u00f3n completa",
"type": "main",
"index": 0
}
],
[
{
"node": "[Redis] SET Sesi\u00f3n continua",
"type": "main",
"index": 0
}
]
]
},
"[Redis] SET Sesi\u00f3n completa": {
"main": [
[
{
"node": "[Respond] Test procesando",
"type": "main",
"index": 0
},
{
"node": "[HTTP] Llamar an\u00e1lisis",
"type": "main",
"index": 0
}
]
]
},
"[Redis] SET Sesi\u00f3n continua": {
"main": [
[
{
"node": "[Respond] Chat continua",
"type": "main",
"index": 0
}
]
]
}
},
"meta": {
"templateCredsSetupCompleted": true
},
"settings": {
"executionOrder": "v1"
}
}
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.
openAiApiredis
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
VocTest — Chat Conversacional. Uses redis, lmChatOpenAi, memoryRedisChat, agent. Webhook trigger; 18 nodes.
Source: https://github.com/zvMateo/LumIA-platform/blob/ea31c391058967949c27f6fbd1af5a2abdf7758c/workflows/02-chat-fixed.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.
My workflow 7. Uses openAi, redis, httpRequest, agent. Webhook trigger; 77 nodes.
Flux. Uses lmChatOpenAi, agent, googleGemini, httpRequest. Webhook trigger; 67 nodes.
Agent: IPTV (instance_e2165d22_1762376395079). Uses openAi, redis, supabase, httpRequest. Webhook trigger; 56 nodes.
VocTest — Chat Conversacional. Uses redis, lmChatOpenAi, memoryRedisChat, agent. Webhook trigger; 18 nodes.
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.