AutomationFlowsSlack & Telegram › Whatsapp Assistant Base - P1 (http Request)

Whatsapp Assistant Base - P1 (http Request)

WhatsApp Assistant Base - P1. Uses httpRequest, supabase, whatsApp. Webhook trigger; 20 nodes.

Webhook trigger★★★★☆ complexity20 nodesHTTP RequestSupabaseWhatsApp
Slack & Telegram Trigger: Webhook Nodes: 20 Complexity: ★★★★☆ Added:

This workflow follows the HTTP Request → Supabase 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
{
  "name": "WhatsApp Assistant Base - P1",
  "nodes": [
    {
      "parameters": {
        "httpMethod": [
          "GET",
          "POST"
        ],
        "path": "whatsapp",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "3933499b-611b-4ae6-aa7d-492daae3aafd",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -480,
        128
      ],
      "multipleMethods": true
    },
    {
      "parameters": {
        "conditions": [
          {
            "id": "1",
            "leftValue": "={{ $json.body && $json.body.entry ? 'false' : 'true' }}",
            "rightValue": "true",
            "operator": "string"
          }
        ],
        "options": {}
      },
      "id": "ce11aee4-1d4d-4af8-8718-ab345f6c3d20",
      "name": "IF Verificaci\u00f3n",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -240,
        128
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $json.query['hub.challenge'] }}",
        "options": {}
      },
      "id": "eaa96758-c098-4057-9bac-647efbd4244e",
      "name": "Responder Challenge",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const mode = String($env.WEBHOOK_VERIFY_SIGNATURE || 'false').toLowerCase();\nconst passthrough = (verified, m) => [{ json: Object.assign({ verified, mode: m }, $input.first().json) }];\nif (mode === 'false') { return passthrough(true, 'disabled'); }\nconst secret = $env.META_APP_SECRET || '';\nconst headers = $input.first().json.headers || {};\nconst signature = headers['x-hub-signature-256'] || headers['X-Hub-Signature-256'] || '';\nconst rawBody = JSON.stringify($input.first().json.body);\nif (!secret) { return passthrough(false, 'missing_secret'); }\nconst run = async () => { try { const encoder = new TextEncoder(); const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']); const sig = await crypto.subtle.sign('HMAC', key, encoder.encode(rawBody)); const hex = Array.from(new Uint8Array(sig)).map(b => b.toString(16).padStart(2, '0')).join(''); return passthrough(signature === 'sha256=' + hex, 'enabled'); } catch (e) { return passthrough(false, 'crypto_unavailable'); } };\nreturn run();"
      },
      "id": "9fe92ab8-35f4-452c-ad12-e474ba45fcb8",
      "name": "Verificar Firma Meta",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -240,
        368
      ]
    },
    {
      "parameters": {
        "conditions": [
          {
            "id": "1",
            "leftValue": "={{ $json.verified ? 'true' : 'false' }}",
            "rightValue": "true",
            "operator": "string"
          }
        ],
        "options": {}
      },
      "id": "f3a760a5-e0e6-4f51-bc69-c0e41a6f48c4",
      "name": "IF Firma OK",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        0,
        368
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "unauthorized",
        "options": {}
      },
      "id": "9fdfa79e-84ff-49ba-8930-96be53e19559",
      "name": "Responder Rechazo",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        240,
        368
      ]
    },
    {
      "parameters": {
        "jsCode": "// Normaliza el payload; los eventos no-mensaje (status, tipos raros, propio negocio) terminan en silencio (return []).\nconst payload = $input.first().json;\nconst businessPhone = ($env.WHATSAPP_BUSINESS_PHONE || '').replace(/\\D/g, '');\nfor (const entry of payload.body.entry || payload.entry || []) {\n  for (const change of entry.changes || []) {\n    const value = change.value || {};\n    if (value.messages && value.messages.length > 0) {\n      const msg = value.messages[0];\n      const from = String(msg.from || '').replace(/\\D/g, '');\n      if (businessPhone && from === businessPhone) { return []; }\n      const hasText = msg.text && msg.text.body;\n      if (msg.type && msg.type !== 'text' && !hasText) {\n      const label = { image: '\ud83d\uddbc\ufe0f [El cliente envio una imagen]', audio: '\ud83c\udfa4 [El cliente envio un audio]', video: '\ud83c\udfac [El cliente envio un video]', document: '\ud83d\udcc4 [El cliente envio un documento]' }[msg.type] || '\ud83d\udcce [El cliente envio un adjunto]';\n      return [{ json: { valid: true, contact_phone: from, message_text: label, message_id: msg.id || '', timestamp: msg.timestamp || '', type: msg.type || 'media' } }];\n    }\n      if (!hasText) { return []; }\n      return [{ json: { valid: true, contact_phone: from, message_text: String(msg.text.body).slice(0, 2000), message_id: msg.id || '', timestamp: msg.timestamp || '', type: msg.type || 'text' } }];\n    }\n    if (value.statuses && value.statuses.length > 0) { return []; }\n  }\n}\nreturn [];"
      },
      "id": "d99227e8-6b2e-43cf-97f1-cc528adda5a8",
      "name": "Normalizar Mensaje",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        208
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SUPABASE_URL + '/rest/v1/contactos?on_conflict=tenant_id,telefono' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.SUPABASE_SERVICE_KEY }}"
            },
            {
              "name": "Authorization",
              "value": "={{ \"Bearer \" + $env.SUPABASE_SERVICE_KEY }}"
            },
            {
              "name": "Prefer",
              "value": "resolution=merge-duplicates,return=representation"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { telefono: $json.contact_phone, tenant_id: $env.TENANT_ID } }}",
        "options": {}
      },
      "id": "9753a2a0-c8d6-4965-84af-08627624afbb",
      "name": "Upsert Contacto",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        480,
        208
      ]
    },
    {
      "parameters": {
        "jsCode": "// Encuentra la fila del contacto en cualquier forma de respuesta (array, body, anidado).\nconst findRow = (x) => {\n  if (Array.isArray(x)) { for (const it of x) { const f = findRow(it); if (f) return f; } return null; }\n  if (x && typeof x === 'object') {\n    if (x.id && (x.telefono || x.tenant_id)) return x;\n    for (const v of Object.values(x)) { const f = findRow(v); if (f) return f; }\n  }\n  return null;\n};\nconst row = findRow($json) || {};\nif (!row.id) { throw new Error('UPSERT-FALLO: ' + JSON.stringify($json).slice(0, 300)); }\nreturn [{ json: { contacto_id: row.id, tenant_id: row.tenant_id || null } }];"
      },
      "id": "dfef1f29-2828-4d4a-b342-4b37d5c34c50",
      "name": "Contacto Listo",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        208
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "conversaciones",
        "limit": 20,
        "orderBy": "created_at.desc",
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "contacto_id",
              "condition": "eq",
              "keyValue": "={{ $('Contacto Listo').item.json.contacto_id }}"
            }
          ]
        }
      },
      "id": "ac01e36f-ca00-4edb-9244-6af39c310c13",
      "name": "Obtener Historial",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        960,
        80
      ],
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "tableId": "conversaciones",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "tenant_id",
              "fieldValue": "={{ $('Contacto Listo').item.json.tenant_id }}"
            },
            {
              "fieldId": "contacto_id",
              "fieldValue": "={{ $('Contacto Listo').item.json.contacto_id }}"
            },
            {
              "fieldId": "role",
              "fieldValue": "user"
            },
            {
              "fieldId": "content",
              "fieldValue": "={{ $('Normalizar Mensaje').item.json.message_text }}"
            }
          ]
        }
      },
      "id": "c4b04d27-8664-4882-a705-dcde7a87ed1a",
      "name": "Guardar Mensaje Usuario",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        960,
        320
      ],
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "let historyRows = [];\ntry { historyRows = $('Obtener Historial').all().map(i => i.json).flat(1).filter(r => r && typeof r === 'object' && !Array.isArray(r)); } catch (e) {}\nconst historial = historyRows.slice(-10).map(h => ({ role: h.role === 'assistant' ? 'assistant' : 'user', content: h.content }));\nconst mensajeActual = $('Normalizar Mensaje').item.json.message_text;\nconst systemPrompt = `Eres el asistente virtual de JeanCRG, consultoria de software e inteligencia artificial en Colombia. Representas a Jean, un emprendedor colombiano que vende asistentes IA para WhatsApp, automatizaciones y desarrollo web.\n\nPERSONALIDAD Y TONO:\n- Cercano, respetuoso y con energia positiva, como un vendedor colombiano profesional.\n- Emojis con moderacion (\ud83d\udc4b \ud83d\ude0a \ud83d\ude80 \u2705 \ud83d\udcc5 \ud83d\udcac).\n- Espanol neutro colombiano: \"\u00bfQue negocio tienes?\", \"claro\", \"perfecto\". Usa \"tu\" (nunca \"vos\"), sin jerga extrema ni regionalismos cerrados.\n- Formato WhatsApp: *negrita* para precios y nombres de servicios.\n- Maximo 2-3 frases por mensaje. SIEMPRE termina con UNA pregunta (nunca dos).\n- Nunca uses listas largas: si el cliente pregunta por todos los servicios, menciona los 3 principales y pregunta cual le llama la atencion.\n\nREGLAS DE CONVERSACION:\n1. Saludo: saludas y te presentas SOLO en la primera interaccion de la conversacion. Las siguientes veces respondes directo al tema, como un vendedor que ya saludo.\n2. Si el cliente vuelve a saludar a mitad de conversacion (\"hola\", \"buenas\"), responde amable sin volver a presentarte: \"\u00a1Hola! \u00bfEn que te ayudo?\"\n3. Si te preguntan si eres robot: \"Soy el asistente virtual de JeanCRG \ud83e\udd16 Respondo al instante y con informacion real del negocio. Si prefieres, te conecto con un asesor humano.\"\n4. Objeciones de precio (\"esta caro\", \"no tengo plata\", \"lo voy a pensar\"): responde con empatia y ofrece el *Piloto de 15 dias por $800.000* (100% descontable) o el *Diagnostico tecnologico por $300.000* (descontable). Ejemplo: \"Te entiendo \ud83d\ude0a Por eso tenemos el piloto de 15 dias por *$800.000*, 100% descontable del servicio final. \u00bfTe cuento como funciona?\"\n5. Agradecimientos y cierres (\"gracias\", \"listo\", \"quedo claro\"): responde calido y deja la puerta abierta: \"\u00a1Con gusto! \ud83d\ude0a Cuando quieras avanzamos. \u00bfTe comparto el enlace para agendar tu consulta gratis?\" (solo si hay interes previo).\n6. Preguntas fuera del tema: redirige amablemente a los servicios de JeanCRG.\n7. Errores de tipeo, mensajes sin puntuacion o de voz: responde normal, sin corregir al cliente.\n8. Si preguntan horarios o disponibilidad: sugiere agendar en https://calendly.com/jeancrg/consulta-gratuita con el caso exacto de agendamiento.\n\nSERVICIOS EXACTOS (nunca inventes precios, servicios NI funciones: no menciones pasarelas de pago, cobros, apps ni nada que no este aqui):\n1) *Asistente IA para WhatsApp* desde *$1.800.000* + retainer mensual de *$250.000* (montaje en 5 dias)\n2) *Diagnostico tecnologico* por *$300.000* (descontable)\n3) *Automatizaciones con n8n* desde *$1.500.000*\n4) *Chatbot con datos propios (RAG)* desde *$2.500.000*\n5) *Tienda online* desde *$2.500.000*\n6) *Desarrollo a medida* desde *$3.000.000*\n*Piloto de 15 dias por $800.000* (100% descontable del servicio final).\n\nCASOS ESPECIFICOS (obligatorio, respuestas EXACTAS cuando aplique):\n- PRIMERA interaccion con mensaje que ES solo un saludo simple (sin pregunta): '\u00a1Hola! \ud83d\udc4b Soy el asistente de JeanCRG. Te ayudo con asistentes IA para WhatsApp, automatizaciones y desarrollo web. \u00bfQue negocio tienes?'\n- PRIMERA interaccion que YA trae una pregunta o necesidad concreta (precio, servicio, problema del negocio): responde DIRECTAMENTE su pregunta con el precio y servicio exactos, y cierra preguntando por su negocio. NO repitas la presentacion completa.\n- Conversacion ya iniciada: responde directo y natural al tema, sin saludos ni presentaciones (ejemplo: '\u00a1Claro! El asistente IA para WhatsApp cuesta desde *$1.800.000* y lo dejamos montado en 5 dias. \u00bfQue negocio tienes?')\n- Quiere agendar/cotizar/propuesta: tu respuesta DEBE ser EXACTAMENTE: '\u00a1Perfecto! \ud83d\udcc5 Te comparto el enlace para que elijas el horario que prefieras: https://calendly.com/jeancrg/consulta-gratuita \u00bfTe queda mejor por la manana o por la tarde?' y agrega [LEAD cita] al final. PROHIBIDO pedir dia u hora, PROHIBIDO confirmar horarios, PROHIBIDO mencionar otros servicios en esa respuesta.\n- Pide hablar con un humano o esta frustrado/insatisfecho: tu respuesta DEBE ser EXACTAMENTE: 'Entendido \ud83d\udc64 Un asesor te escribe por este chat en unos minutos. \u00bfMe confirmas tu nombre para que te atienda con tu caso?' y agrega [DERIVAR] al final. PROHIBIDO seguir vendiendo o pedir mas datos.`;\nconst fechaBogota = new Date().toLocaleString('es-CO', { timeZone: 'America/Bogota', weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: true });\nlet notaTiempo = '';\ntry {\n  const ordenadas = historyRows.slice().sort((a, b) => String(a.created_at || '').localeCompare(String(b.created_at || '')));\n  const ultimaTs = ordenadas.length ? ordenadas[ordenadas.length - 1].created_at : null;\n  if (ultimaTs) {\n    const horas = (Date.now() - new Date(ultimaTs).getTime()) / 3600000;\n    if (horas >= 72) notaTiempo = '\\n\\nCONTEXTO TEMPORAL IMPORTANTE: Han pasado ' + Math.round(horas / 24) + ' DIAS desde el ultimo mensaje del cliente. Empieza reconociendo la ausencia con UNA frase corta y calida (ej: \\u00a1Que bueno saber de ti otra vez!) y SOLO DESPUES retoma el tema.';\n    else if (horas >= 20) notaTiempo = '\\n\\nCONTEXTO TEMPORAL: Ha pasado mas de un dia desde el ultimo mensaje del cliente.';\n    else notaTiempo = '\\n\\nCONTEXTO TEMPORAL: Conversacion activa hoy mismo.';\n  }\n} catch (e) {}\nconst sysContent = 'FECHA Y HORA ACTUAL EN COLOMBIA: ' + fechaBogota + '. Usa esta referencia para palabras como hoy, manana o dias de la semana. Nunca inventes fechas.\\n\\n' + systemPrompt + notaTiempo;\nconst messages = [{ role: 'system', content: sysContent }].concat(historial).concat([{ role: 'user', content: mensajeActual }]);\nreturn [{ json: { model: 'deepseek-v4-flash-vision-exp', thinking: { type: 'disabled' }, messages } }];"
      },
      "id": "ad0d4b40-506e-4acc-91d3-f6e7b537e6f4",
      "name": "Preparar Contexto Gemini",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        208
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://opencode.ai/zen/go/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.OPENCODE_API_KEY }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json }}",
        "options": {}
      },
      "id": "fdbefd43-2f6c-4172-a53d-df620a12e21b",
      "name": "Gemini Responder",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1440,
        208
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "notes": "OpenCode GO (suscripcion) -> mimo-v2.5 sin modo pensamiento (antes deepseek-v4-flash, mas caro). Mover key a $env.OPENCODE_API_KEY para produccion."
    },
    {
      "parameters": {
        "jsCode": "const text = (($json.choices && $json.choices[0] && $json.choices[0].message && $json.choices[0].message.content) || '');\nconst cleanRaw = text.replace(/\\[DERIVAR\\]/gi, '').replace(/\\[LEAD[^\\]]*\\]/gi, '').trim().slice(0, 4000);\nconst userText = ($('Normalizar Mensaje').item.json.message_text || '').toLowerCase();\nconst rows = (() => { try { return $('Obtener Historial').all().map(i => i.json).flat(1).filter(r => r && typeof r === 'object' && !Array.isArray(r)); } catch (e) { return [1]; } })();\nconst primerMensaje = !/(cuesta|precio|ayuda|quien|servicio|agenda|pago|pedido|compra|negocio|tienda|vende|vender|gracias|listo)/i.test(userText);\nconst esSaludo = /^(hola|holi|buenas|buen dia|buenos dias|buenas tardes|buenas noches|hi|hello|ey|hey|oie|q mas|que mas|qu[e\u00e9] mas)[\\s!\u00a1?\u00bf.,]*(raza|bebe)?\\s*$/i.test(userText) && primerMensaje;\n// SALUDO (primera vez o de vuelta): respuesta EXACTA fija, sin IA ni marcadores\nif (esSaludo) {\n  const txt = rows.length === 0\n    ? '\u00a1Hola! \ud83d\udc4b Soy el asistente de JeanCRG. Te ayudo con asistentes IA para WhatsApp, automatizaciones y desarrollo web. \u00bfQu\u00e9 negocio tienes?'\n    : '\u00a1Hola! \ud83d\ude0a \u00bfEn qu\u00e9 te ayudo?';\n  return [{ json: { raw: text, clean: txt, needs_human: false, lead_marker: '', evento: 'none', determinista: true } }];\n}\nconst HUMAN_RE = /(hablar con (alguien|un humano|una persona|un asesor|alguien real)|asesor humano|persona real|no me convence|no me gusta|queja|quejarme|molest|frustrad|insatisfech|ineficiente|mal servicio|estafa|representante|me canse|aburrid|quien me atiende|hay alguien|atendeme|quiero a alguien|llamen|llamame)/i;\nconst LEAD_RE = /(agendar|cita|reuni[o\u00f3]n|diagn[o\u00f3]stic|cotiz|propuest|presupuest|quiero (comprar|contratar|empezar)|me interesa|cu[\u00e1a]nto cuesta|cu[\u00e1a]nto vale|precio|precios|valor|tarifa|demo|prueba|pruebo|piloto|m[\u00e1a]s informaci[o\u00f3]n|me cuentas|c[o\u00f3]mo funciona|en cu[\u00e1a]nto|ayudame con)/i;\nconst OBJ_RE = /(caro|carita|cara|costoso|no me alcanza|no tengo plata|lo pienso|lo voy a pensar|demasiado|no me sobra|presupuesto apretado|ya tengo otro|lo consulto)/i;\nconst PEDIDO_RE = /(pedido|quiero (pedir|ordenar|encargar|reservar)|me llevo|hacer (un|mi) pedido|domicilio|delivery|a domicilio|talla|disponibilidad)/i;\nconst PAGO_RE = /(pagar|pago|transferenc|nequi|daviplata|davi|tarjeta|bono|cup[o\u00f3]n|promoci[o\u00f3]n|adelanto|consignaci[o\u00f3]n)/i;\nconst marker = (text.match(/\\[LEAD ([^\\]]*)\\]/i) || [null, ''])[1];\nconst aiDeriva = /\\[DERIVAR\\]/i.test(text);\n// DERIVAR solo si el USUARIO pide humano; el [DERIVAR] de la IA solo cuenta si ademas el usuario habla de asesor/humano/persona/llamada\nlet evento = 'none';\nif (HUMAN_RE.test(userText) || (aiDeriva && /(asesor|humano|persona|hablar|llamad)/i.test(userText))) evento = 'derivar';\nelse if (marker || LEAD_RE.test(userText)) evento = 'lead';\nelse if (OBJ_RE.test(userText)) evento = 'objecion';\nelse if (PAGO_RE.test(userText)) evento = 'pago';\nelse if (PEDIDO_RE.test(userText)) evento = 'pedido';\n// si la IA alucino [DERIVAR] sin senal del usuario, usar su texto limpio normal\nreturn [{ json: { raw: text, clean: cleanRaw, needs_human: evento === 'derivar', lead_marker: marker || (LEAD_RE.test(userText) ? 'cita' : ''), evento } }];"
      },
      "id": "e67b628a-231b-4c05-9329-baa70a4a5f05",
      "name": "Extraer Respuesta",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        208
      ]
    },
    {
      "parameters": {
        "tableId": "conversaciones",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "tenant_id",
              "fieldValue": "={{ $('Contacto Listo').item.json.tenant_id }}"
            },
            {
              "fieldId": "contacto_id",
              "fieldValue": "={{ $('Contacto Listo').item.json.contacto_id }}"
            },
            {
              "fieldId": "role",
              "fieldValue": "assistant"
            },
            {
              "fieldId": "content",
              "fieldValue": "={{ $json.clean }}"
            }
          ]
        }
      },
      "id": "e7dfceeb-ade9-45f1-84ad-3d4f60a5332b",
      "name": "Guardar Respuesta IA",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1920,
        208
      ],
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "send",
        "phoneNumberId": "=1309447485585504",
        "recipientPhoneNumber": "={{ $('Normalizar Mensaje').item.json.contact_phone }}",
        "textBody": "={{ $('Extraer Respuesta').item.json.clean }}",
        "additionalFields": {}
      },
      "id": "9a37085a-a6d1-417e-bf92-de713459c79d",
      "name": "Enviar WhatsApp",
      "type": "n8n-nodes-base.whatsApp",
      "typeVersion": 1.1,
      "position": [
        2160,
        320
      ],
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Emite es_lead + datos; sin throw (el insert se hace no-op con body [] cuando no hay lead).\nconst lead = $('Extraer Respuesta').item.json.lead_marker || '';\nconst parts = lead ? String(lead).split('|') : [];\nreturn [{ json: {\n  es_lead: Boolean(lead),\n  nombre: (parts[0] || '').trim(),\n  telefono: $('Normalizar Mensaje').item.json.contact_phone,\n  interes: (parts[2] || parts[0] || '').trim()\n} }];"
      },
      "id": "6b848995-1e2a-4b32-ba52-7dce59512f0e",
      "name": "Preparar Lead",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2160,
        128
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SUPABASE_URL + '/rest/v1/leads' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.SUPABASE_SERVICE_KEY }}"
            },
            {
              "name": "Authorization",
              "value": "={{ \"Bearer \" + $env.SUPABASE_SERVICE_KEY }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.es_lead ? [{ tenant_id: $('Contacto Listo').item.json.tenant_id, contacto_id: $('Contacto Listo').item.json.contacto_id, nombre: $json.nombre, telefono: $json.telefono, interes: $json.interes, estado: 'nuevo', source: 'whatsapp' }] : [] }}",
        "options": {}
      },
      "id": "72d7995f-e24e-440d-ba87-1ef89cfa9b90",
      "name": "Guardar Lead",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2400,
        128
      ],
      "onError": "continueRegularOutput",
      "notes": "Si el lead ya existe (409), se omite silenciosamente y el flujo continua."
    },
    {
      "parameters": {
        "jsCode": "const extra = $('Extraer Respuesta').item.json;\nconst phone = $('Normalizar Mensaje').item.json.contact_phone;\nconst evento = extra.evento || 'none';\nconst emojis = { derivar: '\ud83d\udea8 DERIVAR', lead: '\ud83d\udcc5 LEAD', objecion: '\u26a1 OBJECI\u00d3N', pedido: '\ud83d\uded2 PEDIDO', pago: '\ud83d\udcb0 PAGO' };\nconst notificar = evento !== 'none';\nconst texto = notificar ? (emojis[evento] + ': ' + phone + ' | ' + extra.clean) : '';\nreturn [{ json: { notificar, texto } }];"
      },
      "id": "44e91366-7fd5-4456-b13f-6aa8eeabe626",
      "name": "Preparar Notificacion",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2640,
        80
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot8807842110:AAGgU6QDADaN-Lvt_hqZV_orFIhG0dwrT1E/sendMessage' }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.notificar ? { chat_id: 7309831214, text: $json.texto } : {} }}",
        "options": {}
      },
      "id": "1607aabe-af32-4226-bf19-d21b874d20e7",
      "name": "Enviar Notificacion",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2880,
        80
      ],
      "onError": "continueRegularOutput",
      "notes": "DEMO: token del bot embebido. PRODUCCION: mover a $env.TELEGRAM_BOT_TOKEN en Railway."
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "IF Verificaci\u00f3n",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Verificar Firma Meta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Verificaci\u00f3n": {
      "main": [
        [
          {
            "node": "Responder Challenge",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Verificar Firma Meta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verificar Firma Meta": {
      "main": [
        [
          {
            "node": "IF Firma OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Firma OK": {
      "main": [
        [
          {
            "node": "Normalizar Mensaje",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Responder Rechazo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalizar Mensaje": {
      "main": [
        [
          {
            "node": "Upsert Contacto",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert Contacto": {
      "main": [
        [
          {
            "node": "Contacto Listo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Contacto Listo": {
      "main": [
        [
          {
            "node": "Obtener Historial",
            "type": "main",
            "index": 0
          },
          {
            "node": "Guardar Mensaje Usuario",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardar Mensaje Usuario": {
      "main": [
        [
          {
            "node": "Preparar Contexto Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Contexto Gemini": {
      "main": [
        [
          {
            "node": "Gemini Responder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Responder": {
      "main": [
        [
          {
            "node": "Extraer Respuesta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extraer Respuesta": {
      "main": [
        [
          {
            "node": "Guardar Respuesta IA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardar Respuesta IA": {
      "main": [
        [
          {
            "node": "Preparar Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Lead": {
      "main": [
        [
          {
            "node": "Guardar Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardar Lead": {
      "main": [
        [
          {
            "node": "Preparar Notificacion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Notificacion": {
      "main": [
        [
          {
            "node": "Enviar Notificacion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enviar Notificacion": {
      "main": [
        [
          {
            "node": "Enviar WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  }
}

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

WhatsApp Assistant Base - P1. Uses httpRequest, supabase, whatsApp. Webhook trigger; 20 nodes.

Source: https://github.com/JeanCardozo/chatAI/blob/main/workflows/whatsapp-assistant-v5-zen.json — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

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

Slack & Telegram

WhatsApp Assistant Base — P1. Uses supabase, httpRequest, telegram, whatsApp. Webhook trigger; 22 nodes.

Supabase, HTTP Request, Telegram +1
Slack & Telegram

WhatsApp Assistant Base - P1. Uses httpRequest, supabase, whatsApp. Webhook trigger; 20 nodes.

HTTP Request, Supabase, WhatsApp
Slack & Telegram

Course Bot - Didar CRM (v6). Uses n8n-nodes-didar-crm, httpRequest. Webhook trigger; 77 nodes.

N8N Nodes Didar Crm, HTTP Request
Slack & Telegram

Advanced Slackbot With N8N. Uses slack, httpRequest, stickyNote, executeWorkflow. Webhook trigger; 34 nodes.

Slack, HTTP Request, Execute Workflow Trigger +1
Slack & Telegram

Slackbots are super powerful. At n8n, we have been using them to get a lot done.. But it can become hard to manage and maintain many different operations that a workflow can do.

Slack, HTTP Request, Execute Workflow Trigger +1