AutomationFlowsSlack & Telegram › Whatsapp Assistant Base - P1

Whatsapp Assistant Base - P1

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": "n1",
      "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": "n2",
      "name": "IF Verificaci\u00f3n",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -240,
        128
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $json.query['hub.challenge'] }}",
        "options": {}
      },
      "id": "n3",
      "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": "n4",
      "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": "n5",
      "name": "IF Firma OK",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        0,
        368
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "unauthorized",
        "options": {}
      },
      "id": "n6",
      "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) { return []; }\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": "n7",
      "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": {},
        "specifyHeaders": "keypair",
        "contentType": "json"
      },
      "id": "n8",
      "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": "n9",
      "name": "Contacto Listo",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        208
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "conversaciones",
        "returnAll": false,
        "limit": 20,
        "orderBy": "created_at.desc",
        "filterType": "manual",
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "contacto_id",
              "condition": "eq",
              "keyValue": "={{ $('Contacto Listo').item.json.contacto_id }}"
            }
          ]
        }
      },
      "id": "n10",
      "name": "Obtener Historial",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        960,
        80
      ]
    },
    {
      "parameters": {
        "tableId": "conversaciones",
        "dataToSend": "defineBelow",
        "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": "n11",
      "name": "Guardar Mensaje Usuario",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        960,
        320
      ]
    },
    {
      "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 contents = historyRows.map(h => ({ role: h.role === 'assistant' ? 'model' : 'user', parts: [{ text: h.content }] }));\ncontents.push({ role: 'user', parts: [{ text: $('Normalizar Mensaje').item.json.message_text }] });\nconst systemInstruction = $env.SYSTEM_PROMPT || \"Eres el asistente virtual de JeanCRG, una consultoria de software e inteligencia artificial en Colombia. Estilo: cercano y con energia, usas emojis con moderacion (\ud83d\udc4b \ud83d\ude80 \u2705 \ud83d\udcb0 \ud83d\udcf2) y formato de WhatsApp (*negrita* para precios y servicios, _cursiva_ para enfatizar). Respondes SIEMPRE en espanol neutro, en maximo 2-3 frases, y TERMINAS con una pregunta para mantener la conversacion. Nunca des listas largas: si preguntan por servicios, presenta 2-3 opciones y pregunta cual les interesa.\\n\\nSERVICIOS Y PRECIOS (no inventes otros): 1) *Asistente IA para WhatsApp* desde *$1.800.000* + retainer mensual de *$250.000* (montaje en 5 dias) 2) *Diagnostico tecnologico* por *$300.000* (descontable) 3) *Automatizaciones con n8n* desde *$1.500.000* 4) *Chatbot con datos propios (RAG)* desde *$2.500.000* 5) *Tienda online* desde *$2.500.000* 6) *Desarrollo a medida* desde *$3.000.000*. *Piloto de 15 dias por $800.000* (descontable del precio final).\\n\\nREGLA DE LEAD: si el cliente quiere agendar, recibir propuesta o cotizacion, responde amablemente y agrega al final de tu respuesta la marca exacta: [LEAD cita].\\nREGLA DE DERIVACION: si el cliente esta frustrado, insatisfecho o pide hablar con un humano, responde con empatia y agrega al final la marca exacta: [DERIVAR].\\nREGLA DE OPT-OUT: si el cliente escribe STOP o BAJA, confirma que no volvera a recibir mensajes y no insistas.\\nLas marcas [LEAD ...] y [DERIVAR] son internas: nunca las expliques al cliente, solo agregalas al final.\";\nreturn [{ json: { model: 'gemini-flash-latest', contents, system_instruction: { parts: [{ text: systemInstruction }] } } }];"
      },
      "id": "n12",
      "name": "Preparar Contexto Gemini",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        208
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-goog-api-key",
              "value": "={{ $env.GEMINI_API_KEY }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json }}",
        "options": {},
        "specifyHeaders": "keypair",
        "contentType": "json"
      },
      "id": "n13",
      "name": "Gemini Responder",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1440,
        208
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000
    },
    {
      "parameters": {
        "jsCode": "const text = ($json.candidates && $json.candidates[0] && $json.candidates[0].content && $json.candidates[0].content.parts[0].text) || '';\nconst clean = text.replace(/\\[DERIVAR\\]/gi, '').replace(/\\[LEAD[^\\]]*\\]/gi, '').trim().slice(0, 4000);\nreturn [{ json: { raw: text, clean, needs_human: /\\[DERIVAR\\]/i.test(text), lead_marker: (text.match(/\\[LEAD ([^\\]]*)\\]/i) || [null, ''])[1] } }];"
      },
      "id": "n14",
      "name": "Extraer Respuesta",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        208
      ]
    },
    {
      "parameters": {
        "tableId": "conversaciones",
        "dataToSend": "defineBelow",
        "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": "n15",
      "name": "Guardar Respuesta IA",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1920,
        208
      ]
    },
    {
      "parameters": {
        "operation": "send",
        "phoneNumberId": "=1277072472156993",
        "recipientPhoneNumber": "={{ $('Normalizar Mensaje').item.json.contact_phone }}",
        "textBody": "={{ $('Extraer Respuesta').item.json.clean }}",
        "additionalFields": {}
      },
      "id": "n17",
      "name": "Enviar WhatsApp",
      "type": "n8n-nodes-base.whatsApp",
      "typeVersion": 1.1,
      "position": [
        2160,
        320
      ]
    },
    {
      "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": "n18",
      "name": "Preparar Lead",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2160,
        120
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SUPABASE_URL + '/rest/v1/leads' }}",
        "sendHeaders": true,
        "specifyHeaders": "keypair",
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.SUPABASE_SERVICE_KEY }}"
            },
            {
              "name": "Authorization",
              "value": "={{ \"Bearer \" + $env.SUPABASE_SERVICE_KEY }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "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": "n19b",
      "name": "Guardar Lead",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2400,
        120
      ]
    },
    {
      "parameters": {
        "jsCode": "// Solo notifica cuando Gemini marco [DERIVAR].\nconst extra = $('Extraer Respuesta').item.json;\nconst notificar = Boolean(extra.needs_human);\nconst texto = notificar ? 'DERIVAR: contacto ' + $('Normalizar Mensaje').item.json.contact_phone + ' | ' + extra.clean : '';\nreturn [{ json: { notificar, texto } }];"
      },
      "id": "n20",
      "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",
        "contentType": "json",
        "jsonBody": "={{ $json.notificar ? { chat_id: 7309831214, text: $json.texto } : {} }}",
        "options": {}
      },
      "id": "n21",
      "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
  }
}
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-v4-FINAL.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