AutomationFlowsWeb Scraping › Wm-chat-main-2b.n8n-import

Wm-chat-main-2b.n8n-import

Wm-Chat-Main-2B.N8N-Import. Uses httpRequest, redis. Webhook trigger; 19 nodes.

Webhook trigger★★★★☆ complexity19 nodesHTTP RequestRedis
Web Scraping Trigger: Webhook Nodes: 19 Complexity: ★★★★☆ Added:

This workflow follows the HTTP Request → Redis 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 →

Download .json
{
  "nodes": [
    {
      "id": "a57306b8-6bb8-4bc9-8d03-20800709284f",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        400
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "wm-chat-v4",
        "responseMode": "onReceived",
        "options": {
          "responseData": "noData",
          "responseCode": 200
        }
      }
    },
    {
      "id": "c4948124-f89c-428e-b734-1c0e61c4bebf",
      "name": "Parse iHelp Body",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        400
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const body = $json.body || {};\nconst contact = body.contact || {};\nconst tel = String(contact.number || '').replace(/\\D/g, '');\nconst push_name = String(contact.pushName || '').trim();\nconst accumulated_text = String(body.accumulatedText || '').trim();\nconst messages = Array.isArray(body.messages) ? body.messages : [];\nconst audio_urls = messages.filter(m => String(m.type) === '4' && m.mediaUrl).map(m => 'https://images.ihelpchat.com/' + m.mediaUrl);\nconst attendance_id_ref = String(body.attendanceIdRef || '');\nconst now_ms = Date.now();\nif (!tel || tel.length < 10) {\n  return { json: { ok: false, error: 'invalid_phone', tel } };\n}\nreturn { json: { ok: true, tel, push_name, accumulated_text, audio_urls, attendance_id_ref, now_ms } };"
      }
    },
    {
      "id": "f8cf0cb2-ec2f-48fe-98d4-93ae0799524c",
      "name": "Preprocess",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "https://ia-n8n.clalha.easypanel.host/webhook/wm-pre-v3",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ accumulated_text: $json.accumulated_text, audio_urls: $json.audio_urls }) }}",
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "ab09b767-38c0-4a14-b23e-d7e9a3f36d37",
      "name": "Merge Text",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        400
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const up = $('Parse iHelp Body').item.json;\nconst pp = $json;\nreturn { json: { ...up, text: pp.text || up.accumulated_text, has_audios: !!pp.has_audios, transcripts: pp.transcripts || [] } };"
      }
    },
    {
      "id": "a2ad068c-0259-4ea4-af2e-6d8ac1f6f180",
      "name": "Get State",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "https://ia-n8n.clalha.easypanel.host/webhook/wm-se-v3",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ tel: $json.tel, action: 'get', params: {} }) }}",
        "options": {
          "timeout": 10000
        }
      }
    },
    {
      "id": "456c6a2f-4f28-4762-a5ff-eff0174f60ff",
      "name": "Merge State",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        400
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const up = $('Merge Text').item.json;\nconst st = $json;\nreturn { json: { ...up, state: st.state, state_existed: st.extra && st.extra.state_existed } };"
      }
    },
    {
      "id": "6be34b8b-0b4d-4c18-922d-bd477aeaa291",
      "name": "Guard Bot Active",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1320,
        400
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "c_enabled",
              "leftValue": "={{ $json.state.bot.enabled }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "id": "c_notpaused",
              "leftValue": "={{ ($json.state.bot.paused_until_ms || 0) < $json.now_ms }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "2d3be06f-584b-4190-bd9f-512b6b549758",
      "name": "Classifier LLM",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1540,
        300
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({\n  model: 'gpt-4.1',\n  temperature: 0.1,\n  max_tokens: 250,\n  response_format: { type: 'json_object' },\n  messages: [\n    { role: 'system', content: `Voc\u00ea analisa mensagens de WhatsApp pra W.Music (gravadora gospel). Retorne SOMENTE JSON v\u00e1lido com estes campos:\\n\\n- push_name_util (boolean): o push_name parece ser um nome real de pessoa? false se for emoji, vers\u00edculo, refer\u00eancia b\u00edblica, \"Am\u00e9m\", \"Paz\", \"Deus te aben\u00e7oe\", frase longa (>4 palavras), apelido estranho, ou vazio.\\n- name_extracted (string|null): se o TEXTO DO LEAD cont\u00e9m apresenta\u00e7\u00e3o de nome (\"sou Bruno\", \"me chamo Jo\u00e3o\", \"aqui \u00e9 Vagner\", \"\u00e9 o Pedro\"), extraia apenas o primeiro nome capitalizado. Caso contr\u00e1rio, null.\\n- aulas_done (boolean): o lead indica ter conclu\u00eddo as aulas? Ex: \"assisti todas\", \"terminei\", \"vi as aulas\", \"finalizei\", \"j\u00e1 assisti\", \"parab\u00e9ns pelas aulas\".\\n- schedule_tomorrow (boolean): o lead pede pra come\u00e7ar amanh\u00e3/outro dia? Ex: \"amanh\u00e3\", \"outro dia\", \"depois\", \"vou come\u00e7ar amanh\u00e3\".\\n- retry_prova (boolean): o lead pede pra refazer a prova? Ex: \"quero tentar de novo\", \"refazer\", \"me manda de novo o link\".\\n\\nSa\u00edda: JSON puro, sem markdown, sem explica\u00e7\u00e3o.` },\n    { role: 'user', content: `push_name: \"${$json.push_name || ''}\"\\ntext: \"${$json.text || ''}\"\\njourney_step: \"${$json.state.journey.step}\"` }\n  ]\n}) }}",
        "options": {
          "timeout": 20000
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "e9db875b-ab13-451f-8fc6-6432e4a7f7cb",
      "name": "Parse Classifier",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1760,
        300
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const up = $('Merge State').item.json;\nlet parsed = { push_name_util: false, name_extracted: null, aulas_done: false, schedule_tomorrow: false, retry_prova: false };\ntry {\n  const content = $json.choices[0].message.content;\n  const p = JSON.parse(content);\n  parsed.push_name_util = !!p.push_name_util;\n  parsed.name_extracted = p.name_extracted && typeof p.name_extracted === 'string' ? p.name_extracted.trim().slice(0, 60) : null;\n  parsed.aulas_done = !!p.aulas_done;\n  parsed.schedule_tomorrow = !!p.schedule_tomorrow;\n  parsed.retry_prova = !!p.retry_prova;\n} catch (e) {\n  parsed._error = String(e).slice(0, 200);\n}\nreturn { json: { ...up, classifier: parsed } };"
      }
    },
    {
      "id": "11ced2d3-1563-4c6a-8df7-7fe550c7169f",
      "name": "Route Decide",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1980,
        300
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const up = $json;\nconst step = up.state.journey.step;\nconst cls = up.classifier;\nconst firstName = (cls.name_extracted || up.state.lead_profile.name_for_use || up.push_name || '').split(/\\s+/)[0];\n\n// Period calc: SP = UTC-3. Correct boundaries\nconst spHour = (new Date().getUTCHours() - 3 + 24) % 24;\nlet period;\nif (spHour >= 5 && spHour < 12) period = 'Bom dia';\nelse if (spHour >= 12 && spHour < 18) period = 'Boa tarde';\nelse period = 'Boa noite';\n\nlet maestro_enabled = false;\nconst plan = { stage_actions: [], reply_text: null, transitions: [], schedule_action: null };\n\nfunction next8amSP_ms() {\n  const d = new Date();\n  d.setUTCHours(11, 0, 0, 0);\n  if (Date.now() > d.getTime()) d.setUTCDate(d.getUTCDate() + 1);\n  return d.getTime();\n}\n\nconst CHAIN_ACTIONS = [\n  {kind:'move_stage', params:{stage_id:'706'}},\n  {kind:'send_audio', params:{audio_key:'abertura_1'}},\n  {kind:'send_audio', params:{audio_key:'abertura_2'}},\n  {kind:'send_aulas_link', params:{}}\n];\n\nif (step === 'new') {\n  // Hardcoded greeting: 2 bubbles (sauda\u00e7\u00e3o + firstname aposta)\n  let reply = period + '. A paz do Senhor.';\n  if (cls.push_name_util && firstName) {\n    reply += '\\n\\n' + firstName + '?';\n  }\n  plan.reply_text = reply;\n  plan.transitions = [{ intent: 'needs_name', push_name: up.push_name || null }];\n} else if (step === 'apresentando') {\n  maestro_enabled = true;\n} else if ((step === 'abertura_enviada' || step === 'aguardando_aulas') && cls.aulas_done) {\n  const transitions = [];\n  if (step === 'abertura_enviada') transitions.push({ intent: 'any_msg' });\n  transitions.push({ intent: 'aulas_done' });\n  transitions.push({ intent: 'send_tally' });\n  plan.transitions = transitions;\n  plan.stage_actions = [\n    {kind:'send_text', params:{text: 'Vou te enviar o question\u00e1rio'}},\n    {kind:'move_stage', params:{stage_id:'707'}},\n    {kind:'send_tally_link', params:{}}\n  ];\n  plan.reply_text = null;\n} else if ((step === 'abertura_enviada' || step === 'aguardando_aulas') && cls.schedule_tomorrow) {\n  const transitions = [];\n  if (step === 'abertura_enviada') transitions.push({ intent: 'any_msg' });\n  transitions.push({ intent: 'schedule_tomorrow' });\n  plan.transitions = transitions;\n  plan.reply_text = 'Perfeito! Amanh\u00e3 \u00e0s 8h eu te envio uma mensagem pra come\u00e7armos';\n  plan.schedule_action = { kind: 'wake_aulas', due_at_ms: next8amSP_ms(), attempt: 0 };\n} else if (step === 'aguardando_revisao' && cls.retry_prova) {\n  plan.transitions = [{ intent: 'retry' }];\n  plan.stage_actions = [{kind:'send_tally_link', params:{}}];\n  plan.reply_text = firstName ? 'Segue o link pra voc\u00ea refazer, ' + firstName : 'Segue o link pra voc\u00ea refazer';\n} else if (step === 'abertura_enviada') {\n  maestro_enabled = true;\n  plan.transitions = [{ intent: 'any_msg' }];\n} else {\n  maestro_enabled = true;\n}\n\nreturn { json: { ...up, route: { maestro_enabled, plan, first_name_hint: firstName || null, period_hint: period, push_name_util: !!cls.push_name_util } } };"
      }
    },
    {
      "id": "8c7d4424-21ac-49fd-b30c-0e668a3d2c69",
      "name": "IF Maestro",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2200,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "c_maestro",
              "leftValue": "={{ $json.route.maestro_enabled }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "9cbb223c-a3fb-4420-83f2-54851fcc89c8",
      "name": "Maestro LLM",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2420,
        200
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json.maestro_body) }}",
        "options": {
          "timeout": 20000
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "63a39373-e537-4781-9e29-4ea0b3dd04bb",
      "name": "Patch Plan Maestro",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2640,
        200
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const route = $('Route Decide').item.json;\nlet maestro_out = null;\ntry {\n  maestro_out = JSON.parse($json.choices[0].message.content);\n} catch(e) {\n  maestro_out = { reply_text: 'Recebi sua mensagem, em instantes te respondo', action: 'reply' };\n}\n\nconst plan = { ...route.route.plan };\nconst maestroText = (maestro_out.reply_text && maestro_out.reply_text.trim()) || 'Recebi sua mensagem, em instantes te respondo';\nlet clear_counter = false;\n\nif (maestro_out.action === 'fire_cadeia' && route.state.journey.step === 'apresentando') {\n  const extracted = (maestro_out.extracted_name && maestro_out.extracted_name.trim()) || null;\n  const firstPushName = (route.push_name || '').split(/\\s+/)[0] || null;\n  const nameForUse = extracted || route.state.lead_profile.name_for_use || firstPushName;\n  \n  plan.stage_actions = [\n    {kind:'send_text', params:{text: maestroText}},\n    {kind:'move_stage', params:{stage_id:'706'}},\n    {kind:'send_audio', params:{audio_key:'abertura_1'}},\n    {kind:'send_aulas_link', params:{}},\n    {kind:'send_audio', params:{audio_key:'abertura_2'}}\n  ];\n  plan.transitions = [{\n    intent: 'name_extracted',\n    name_for_use: nameForUse,\n    push_name: route.push_name || nameForUse\n  }];\n  plan.reply_text = null;\n  clear_counter = true;  // reset apres_turn after successful cadeia\n} else {\n  plan.reply_text = maestroText;\n}\n\nreturn { json: { ...route, route: { ...route.route, plan }, clear_counter } };"
      }
    },
    {
      "id": "cb6b1b26-4c45-44d8-aa09-9880f1f18517",
      "name": "Execute Plan",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2860,
        300
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const up = $json;\nconst plan = up.route.plan;\nconst tel = up.tel;\nconst STAGE_URL = 'https://ia-n8n.clalha.easypanel.host/webhook/wm-actions-v3';\nconst STATE_URL = 'https://ia-n8n.clalha.easypanel.host/webhook/wm-se-v3';\nconst hdrs = {'Content-Type': 'application/json'};\nconst results = { actions: [], chunks: 0, transitions: [], scheduled: false };\n\nfunction jitter(min, max) { return min + Math.floor(Math.random() * (max - min)); }\n\nfunction delayFor(kind, isFirst) {\n  if (isFirst) return 0;\n  switch (kind) {\n    case 'send_audio': return jitter(12000, 20000);\n    case 'send_aulas_link': return jitter(6000, 10000);\n    case 'send_tally_link': return jitter(4000, 7000);\n    case 'send_text': return jitter(2500, 5500);\n    case 'move_stage': return 0;\n    case 'apply_tags': return 0;\n    default: return jitter(800, 1500);\n  }\n}\n\ntry {\n  const actions = plan.stage_actions || [];\n  for (let i = 0; i < actions.length; i++) {\n    const a = actions[i];\n    const delay = delayFor(a.kind, i === 0);\n    if (delay > 0) await new Promise(r => setTimeout(r, delay));\n    const r = await this.helpers.httpRequest({\n      method: 'POST', url: STAGE_URL,\n      body: {kind: a.kind, tel, params: a.params || {}},\n      json: true, headers: hdrs\n    });\n    results.actions.push({kind: a.kind, ok: r && r.ok !== false, delay_ms: delay});\n  }\n\n  if (plan.reply_text) {\n    const chunks = String(plan.reply_text).split(/\\n\\n+/).map(c => c.trim()).filter(Boolean);\n    for (let i = 0; i < chunks.length; i++) {\n      const chunk = chunks[i];\n      let delay = Math.min(600 + chunk.length * 35, 6000) + Math.floor(Math.random() * 400);\n      if (i === 0 && actions.length > 0) delay += jitter(2500, 4500);\n      await new Promise(r => setTimeout(r, delay));\n      await this.helpers.httpRequest({\n        method: 'POST', url: STAGE_URL,\n        body: {kind: 'send_text', tel, params: {text: chunk}},\n        json: true, headers: hdrs\n      });\n      results.chunks++;\n    }\n  }\n\n  for (const t of (plan.transitions || [])) {\n    const r = await this.helpers.httpRequest({\n      method: 'POST', url: STATE_URL,\n      body: {tel, action: 'transition', params: t},\n      json: true, headers: hdrs\n    });\n    results.transitions.push({intent: t.intent, to_step: r && r.extra ? r.extra.to_step : null});\n  }\n\n  if (plan.schedule_action) {\n    await this.helpers.httpRequest({\n      method: 'POST', url: STATE_URL,\n      body: {tel, action: 'schedule_action', params: plan.schedule_action},\n      json: true, headers: hdrs\n    });\n    results.scheduled = true;\n  }\n\n  return { json: { ok: true, tel, maestro: up.route.maestro_enabled, clear_counter: !!up.clear_counter, results } };\n} catch(e) {\n  return { json: { ok: false, tel, error: String(e).slice(0, 300), results, clear_counter: false } };\n}"
      }
    },
    {
      "id": "cbed76a9-eea4-4d36-acb5-75723fac8123",
      "name": "Respond Inactive",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        600
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const s = $json.state || {};\nreturn { json: { ok: true, skipped: true, reason: s.bot && s.bot.enabled === false ? 'bot_disabled' : 'bot_paused', paused_until_ms: s.bot && s.bot.paused_until_ms } };"
      }
    },
    {
      "id": "build-maestro-01",
      "name": "Build Maestro Body",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2200,
        200
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const route = $('Route Decide').item.json;\nconst up = route;\nconst step = up.state.journey.step;\nconst name = up.state.lead_profile.name_for_use || up.route.first_name_hint || up.push_name || null;\nconst period = up.route.period_hint;\n\nconst apres_key = `lead:${up.tel}:apres_turn:${up.state.journey.entered_at_ms}`;\nconst apres_turn = parseInt(($json && $json[apres_key]) || 0, 10) || 0;\n\nconst baseRules = `Voc\u00ea \u00e9 o Maestro Wellington da W.Music (gravadora gospel). Fala em 1\u00aa pessoa, tom evang\u00e9lico natural. Frases MUITO curtas, estilo WhatsApp. NUNCA use \"irm\u00e3o(\u00e3)\" com par\u00eanteses. Use \"irm\u00e3o\" (masc) ou \"irm\u00e3\" (fem) baseado no nome inferido.\n\nJourney step: ${step}.\nNome do lead (se conhecido): ${name || 'ainda n\u00e3o confirmado'}.\nPer\u00edodo: ${period}.\nTurno da apresenta\u00e7\u00e3o: ${apres_turn}.\n\nNUNCA mencione pre\u00e7os, \"personalizado\", \"exclusivo\", IDs, nomes internos.`;\n\nconst apresRules = `\nREGRA APRESENTA\u00c7\u00c3O (step=apresentando):\nVoc\u00ea j\u00e1 mandou sauda\u00e7\u00e3o + aposta no nome. Lead agora responde.\n\n>>> REGRAS HARD, SIGA EXATAMENTE: <<<\n\nTurno 1 (apres_turn=1):\n  - Se lead CONFIRMOU o nome (\"sim\", \"isso\", \"isso mesmo\", \"\u00e9 isso\"):\n    action=reply, reply_text=\"Blz irm\u00e3o??\" (masc) ou \"Blz irm\u00e3??\" (fem). Curt\u00edssimo.\n  - Se lead CORRIGIU o nome (\"sou Bruno\", \"\u00e9 Vagner\", \"me chamo Pedro\", \"n\u00e3o \u00e9 X, \u00e9 Y\"):\n    action=reply, reply_text=\"Blz {nomeExtraido}??\" (preenche extracted_name tamb\u00e9m). Curt\u00edssimo.\n  - Se lead NEGOU o nome apostado (\"n\u00e3o\", \"nao\", \"errado\", \"n\u00e3o sou\") sem oferecer outro:\n    action=reply, reply_text=\"Ah, desculpa! Qual \u00e9 o seu nome?\" \u2014 pergunta direta e curta.\n  - Se lead IGNOROU a pergunta de nome e j\u00e1 fez sauda\u00e7\u00e3o casual (\"gra\u00e7as a Deus tudo bem\", \"tudo \u00f3timo\"):\n    action=fire_cadeia, reply_text=\"Vou te atender bem r\u00e1pido aqui\"\n\nTurno 2 OU MAIOR (apres_turn >= 2):\n  - FORCE action=fire_cadeia, reply_text=\"Vou te atender bem r\u00e1pido aqui\" (ou varia\u00e7\u00e3o curta: \"Beleza, vamos l\u00e1\", \"\u00d3timo, vou te passar o material\").\n  - \u00daNICA EXCE\u00c7\u00c3O: se lead pediu algo CLARAMENTE n\u00e3o relacionado e urgente (ex: \"preciso cancelar\", \"meu pagamento\", \"falar com atendente humano\"). Nesse caso action=reply curto redirecionando.\n  - NUNCA repita \"Blz irm\u00e3o??\" no turno 2+. Isso \u00e9 bug.\n\nSempre preencha extracted_name se souber o nome.`;\n\nconst otherRules = `\nResponda conversacionalmente, curto. action=reply sempre. Pre\u00e7os/datas/propostas \u2192 redirecione (\"essa eu explico melhor mais pra frente\").`;\n\nlet stepRules = otherRules;\nif (step === 'apresentando') stepRules = apresRules;\n\nconst system = baseRules + '\\n' + stepRules + `\n\nRetorne SOMENTE JSON:\n{\n  \"reply_text\": \"...\",\n  \"action\": \"reply\" | \"fire_cadeia\",\n  \"extracted_name\": \"Primeiro nome\" | null\n}`;\n\nconst body = {\n  model: 'gpt-4.1',\n  temperature: 0.5,\n  max_tokens: 300,\n  response_format: { type: 'json_object' },\n  messages: [\n    { role: 'system', content: system },\n    { role: 'user', content: `[CONTEXTO INTERNO: apres_turn=${apres_turn}]\\nMensagem do lead: ${up.text || '(sem texto)'}` }\n  ]\n};\n\nreturn { json: { ...up, apres_turn, maestro_body: body } };"
      }
    },
    {
      "id": "redis-incr-apres-01",
      "name": "Redis INCR Apres",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        2000,
        200
      ],
      "parameters": {
        "operation": "incr",
        "key": "=lead:{{ $json.tel }}:apres_turn:{{ $json.state.journey.entered_at_ms }}",
        "keyType": "integer",
        "propertyName": "apres_turn"
      },
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "if-clear-counter-01",
      "name": "IF Clear Counter",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3080,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "cc",
              "leftValue": "={{ $json.clear_counter }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "redis-del-apres-01",
      "name": "Redis DEL Apres",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        3300,
        300
      ],
      "parameters": {
        "operation": "delete",
        "key": "=lead:{{ $json.tel }}:apres_turn:{{ $json.route.plan.transitions[0].intent ? '' : '' }}"
      },
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Parse iHelp Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse iHelp Body": {
      "main": [
        [
          {
            "node": "Preprocess",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preprocess": {
      "main": [
        [
          {
            "node": "Merge Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Text": {
      "main": [
        [
          {
            "node": "Get State",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get State": {
      "main": [
        [
          {
            "node": "Merge State",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge State": {
      "main": [
        [
          {
            "node": "Guard Bot Active",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guard Bot Active": {
      "main": [
        [
          {
            "node": "Classifier LLM",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Inactive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classifier LLM": {
      "main": [
        [
          {
            "node": "Parse Classifier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Classifier": {
      "main": [
        [
          {
            "node": "Route Decide",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Decide": {
      "main": [
        [
          {
            "node": "IF Maestro",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Maestro": {
      "main": [
        [
          {
            "node": "Redis INCR Apres",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Execute Plan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Maestro LLM": {
      "main": [
        [
          {
            "node": "Patch Plan Maestro",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Patch Plan Maestro": {
      "main": [
        [
          {
            "node": "Execute Plan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Maestro Body": {
      "main": [
        [
          {
            "node": "Maestro LLM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Redis INCR Apres": {
      "main": [
        [
          {
            "node": "Build Maestro Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Plan": {
      "main": [
        [
          {
            "node": "IF Clear Counter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Clear Counter": {
      "main": [
        [
          {
            "node": "Redis DEL Apres",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Wm-Chat-Main-2B.N8N-Import. Uses httpRequest, redis. Webhook trigger; 19 nodes.

Source: https://gist.github.com/bruunofco/b6ec9038ea2975f99392f2d1be934ba9 — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

Response_Handler. Uses httpRequest, dataTable, n8n-nodes-evolution-api, redis. Webhook trigger; 32 nodes.

HTTP Request, Data Table, N8N Nodes Evolution Api +1
Web Scraping

Response_Handler_v2. Uses httpRequest, n8n-nodes-evolution-api, redis. Webhook trigger; 26 nodes.

HTTP Request, N8N Nodes Evolution Api, Redis
Web Scraping

Techno Gas. Uses httpRequest, redis. Webhook trigger; 8 nodes.

HTTP Request, Redis
Web Scraping

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh