AutomationFlowsGeneral › AI Website Chatbot with Gemini

AI Website Chatbot with Gemini

Original n8n title: AI Website Chatbot — Main Handler

AI Website Chatbot — Main Handler. Uses httpRequest. Webhook trigger; 22 nodes.

Webhook trigger★★★★☆ complexity22 nodesHTTP Request
General Trigger: Webhook Nodes: 22 Complexity: ★★★★☆ Added:

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": "AI Website Chatbot \u2014 Main Handler",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "chat",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "node-webhook",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -2752,
        240
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-gemini-key",
              "name": "gemini_api_key",
              "value": "YOUR_GEMINI_API_KEY",
              "type": "string"
            },
            {
              "id": "cfg-gemini-url",
              "name": "gemini_url",
              "value": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -2544,
        240
      ],
      "id": "node-config",
      "name": "Config"
    },
    {
      "parameters": {
        "jsCode": "const sessionId = $('Webhook').item.json.body.session_id;\nconst message = $('Webhook').item.json.body.message;\n\nif (!sessionId || !message || message.trim().length === 0) {\n  throw new Error('Missing session_id or message');\n}\n\nif (message.length > 2000) {\n  throw new Error('Message too long \u2014 max 2000 characters');\n}\n\n// Rate limiting \u2014 max 5 messages per 30 seconds per session\nconst now = Date.now();\nconst WINDOW_MS = 30 * 1000;\nconst MAX_MESSAGES = 5;\nconst CLEANUP_MS = 60 * 1000;\n\nconst workflowStaticData = $getWorkflowStaticData('global');\nif (!workflowStaticData.rateLimits) {\n  workflowStaticData.rateLimits = {};\n}\n\nfor (const sid of Object.keys(workflowStaticData.rateLimits)) {\n  workflowStaticData.rateLimits[sid] = workflowStaticData.rateLimits[sid]\n    .filter(ts => now - ts < CLEANUP_MS);\n  if (workflowStaticData.rateLimits[sid].length === 0) {\n    delete workflowStaticData.rateLimits[sid];\n  }\n}\n\nif (!workflowStaticData.rateLimits[sessionId]) {\n  workflowStaticData.rateLimits[sessionId] = [];\n}\n\nconst recentMessages = workflowStaticData.rateLimits[sessionId]\n  .filter(ts => now - ts < WINDOW_MS);\n\nif (recentMessages.length >= MAX_MESSAGES) {\n  throw new Error(\"You're sending messages too quickly. Please wait a moment and try again.\");\n}\n\nworkflowStaticData.rateLimits[sessionId].push(now);\n\nreturn {\n  json: {\n    session_id: sessionId,\n    message: message.trim()\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2336,
        240
      ],
      "id": "node-validate",
      "name": "Validate Input"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "assign-session",
              "name": "session_id",
              "value": "={{ $('Validate Input').item.json.session_id }}",
              "type": "string"
            },
            {
              "id": "assign-message",
              "name": "message",
              "value": "={{ $('Validate Input').item.json.message }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "node-setvars",
      "name": "Set Variables",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -2112,
        240
      ]
    },
    {
      "parameters": {
        "jsCode": "const sessionId = $('Set Variables').item.json.session_id;\nconst workflowStaticData = $getWorkflowStaticData('global');\nconst session = workflowStaticData.sessions && workflowStaticData.sessions[sessionId];\nconst messages = session ? session.messages : [];\nreturn { json: { messages, session_id: sessionId } };"
      },
      "id": "node-recall",
      "name": "Recall Memory",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1904,
        240
      ]
    },
    {
      "parameters": {
        "jsCode": "const sessionMessage = $('Set Variables').item.json.message;\nconst history = $('Recall Memory').item.json.messages || [];\n\n// ============================================================\n// CUSTOMIZE: Replace this system prompt with your own business\n// details, services, pricing, and conversation rules.\n// ============================================================\nconst systemPrompt = `You are the AI assistant for [YOUR COMPANY NAME]. You help visitors learn about your services, answer questions, and capture leads.\n\nCOMPANY DETAILS:\n- Company: [YOUR COMPANY NAME]\n- Phone: [YOUR PHONE NUMBER]\n- Email: [YOUR EMAIL]\n- Website: [YOUR WEBSITE URL]\n\nSERVICES:\n- [Service 1]: [Description and pricing]\n- [Service 2]: [Description and pricing]\n- [Service 3]: [Description and pricing]\n\nCONVERSATION RULES:\n1. Be professional, helpful, and concise.\n2. Naturally collect the user's name and email during conversation.\n3. Never fabricate information not listed above.\n4. Always recommend contacting the team for complex requirements.`;\n\n// Convert history to Gemini contents format\nconst contents = [];\nfor (const msg of history) {\n  contents.push({\n    role: msg.role === 'assistant' ? 'model' : 'user',\n    parts: [{ text: msg.content }]\n  });\n}\ncontents.push({ role: 'user', parts: [{ text: sessionMessage }] });\n\nreturn {\n  json: {\n    body: JSON.stringify({\n      systemInstruction: {\n        parts: [{ text: systemPrompt }]\n      },\n      contents: contents,\n      generationConfig: {\n        temperature: 0.7,\n        maxOutputTokens: 800\n      }\n    })\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1696,
        240
      ],
      "id": "node-buildreq",
      "name": "Build Request Body"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Config').item.json.gemini_url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "x-goog-api-key",
              "value": "={{ $('Config').item.json.gemini_api_key }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ $json.body }}",
        "options": {}
      },
      "id": "node-gemini",
      "name": "Gemini API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -1472,
        240
      ],
      "retryOnFail": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "check-ok",
              "leftValue": "={{ $json.candidates[0].content.parts[0].text }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -1264,
        240
      ],
      "id": "node-geminiok",
      "name": "Gemini OK?"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "assign-reply",
              "name": "reply",
              "value": "={{ $json.candidates[0].content.parts[0].text }}",
              "type": "string"
            },
            {
              "id": "assign-session-out",
              "name": "session_id",
              "value": "={{ $('Set Variables').item.json.session_id }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "node-extract",
      "name": "Extract Reply",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -1040,
        240
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ reply: $('Extract Reply').item.json.reply, session_id: $('Set Variables').item.json.session_id }) }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "YOUR_WEBSITE_ORIGIN"
              },
              {
                "name": "Access-Control-Allow-Methods",
                "value": "POST, OPTIONS"
              },
              {
                "name": "Access-Control-Allow-Headers",
                "value": "Content-Type"
              },
              {
                "name": "Access-Control-Max-Age",
                "value": "86400"
              }
            ]
          }
        }
      },
      "id": "node-respond",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -608,
        288
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ reply: \"I'm having trouble right now. Please try again or contact us directly.\", session_id: $('Set Variables').item.json.session_id }) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "YOUR_WEBSITE_ORIGIN"
              },
              {
                "name": "Access-Control-Allow-Methods",
                "value": "POST, OPTIONS"
              },
              {
                "name": "Access-Control-Allow-Headers",
                "value": "Content-Type"
              },
              {
                "name": "Access-Control-Max-Age",
                "value": "86400"
              }
            ]
          }
        }
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        -1040,
        496
      ],
      "id": "node-error",
      "name": "Error Response"
    },
    {
      "parameters": {
        "jsCode": "const sessionId = $('Set Variables').item.json.session_id;\nconst userMessage = $('Set Variables').item.json.message;\nconst assistantReply = $('Extract Reply').item.json.reply;\nconst now = Date.now();\nconst TTL_MS = 2 * 60 * 60 * 1000; // 2 hours\n\nconst workflowStaticData = $getWorkflowStaticData('global');\nif (!workflowStaticData.sessions) {\n  workflowStaticData.sessions = {};\n}\n\nif (!workflowStaticData.sessions[sessionId]) {\n  workflowStaticData.sessions[sessionId] = { messages: [], lastActive: now };\n}\n\nworkflowStaticData.sessions[sessionId].messages.push(\n  { role: 'user', content: userMessage },\n  { role: 'assistant', content: assistantReply }\n);\n\nif (workflowStaticData.sessions[sessionId].messages.length > 20) {\n  workflowStaticData.sessions[sessionId].messages =\n    workflowStaticData.sessions[sessionId].messages.slice(-20);\n}\n\nworkflowStaticData.sessions[sessionId].lastActive = now;\n\nfor (const sid of Object.keys(workflowStaticData.sessions)) {\n  const session = workflowStaticData.sessions[sid];\n  const lastActive = session.lastActive || 0;\n  if (now - lastActive > TTL_MS) {\n    delete workflowStaticData.sessions[sid];\n  }\n}\n\nreturn { json: { saved: true, session_id: sessionId } };"
      },
      "id": "node-savemem",
      "name": "Save Memory",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -800,
        416
      ]
    },
    {
      "parameters": {
        "jsCode": "const userMessage = $('Set Variables').item.json.message;\nconst assistantReply = $('Extract Reply').item.json.reply;\nconst history = $('Recall Memory').item.json.messages || [];\n\nconst recentHistory = history.slice(-6);\n\nlet conversationText = '';\nfor (const msg of recentHistory) {\n  const role = msg.role === 'user' ? 'User' : 'Assistant';\n  conversationText += `${role}: ${msg.content}\\n`;\n}\nconversationText += `User: ${userMessage}\\nAssistant: ${assistantReply}`;\n\nreturn {\n  json: {\n    body: JSON.stringify({\n      systemInstruction: {\n        parts: [{ text: \"You are a lead detection assistant. Analyse the conversation and extract lead information if present.\\n\\nReturn ONLY valid JSON in this exact format, no explanation, no markdown:\\n{\\\"has_lead\\\": true/false, \\\"name\\\": \\\"...\\\", \\\"email\\\": \\\"...\\\", \\\"phone\\\": \\\"...\\\", \\\"intent\\\": \\\"...\\\"}\\n\\nSet has_lead to true only if you can identify at least an email address. Leave fields as empty string if not found.\" }]\n      },\n      contents: [\n        {\n          role: \"user\",\n          parts: [{ text: conversationText }]\n        }\n      ],\n      generationConfig: {\n        temperature: 0.1,\n        maxOutputTokens: 200,\n        responseMimeType: \"application/json\"\n      }\n    })\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -800,
        128
      ],
      "id": "node-buildlead",
      "name": "Build Lead Detection Body"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Config').item.json.gemini_url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "x-goog-api-key",
              "value": "={{ $('Config').item.json.gemini_api_key }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ $json.body }}",
        "options": {}
      },
      "id": "node-leaddetect",
      "name": "Lead Detection",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -608,
        128
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.item.json.candidates[0].content.parts[0].text;\nlet parsed;\ntry {\n  const cleaned = raw.replace(/```json|```/g, '').trim();\n  parsed = JSON.parse(cleaned);\n} catch(e) {\n  parsed = { has_lead: false, name: '', email: '', phone: '', intent: '' };\n}\nreturn { json: parsed };"
      },
      "id": "node-parselead",
      "name": "Parse Lead JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -416,
        128
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false
          },
          "conditions": [
            {
              "id": "check-lead",
              "leftValue": "={{ $json.has_lead }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "node-haslead",
      "name": "Has Lead?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -224,
        128
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ld-name",
              "name": "name",
              "value": "={{ $('Parse Lead JSON').item.json.name }}",
              "type": "string"
            },
            {
              "id": "ld-email",
              "name": "email",
              "value": "={{ $('Parse Lead JSON').item.json.email }}",
              "type": "string"
            },
            {
              "id": "ld-intent",
              "name": "intent",
              "value": "={{ $('Parse Lead JSON').item.json.intent }}",
              "type": "string"
            },
            {
              "id": "ld-session",
              "name": "session_id",
              "value": "={{ $('Set Variables').item.json.session_id }}",
              "type": "string"
            },
            {
              "id": "ld-phone",
              "name": "phone",
              "value": "={{ $('Parse Lead JSON').item.json.phone }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        0,
        0
      ],
      "id": "node-preplead",
      "name": "Prepare Lead Data"
    },
    {
      "parameters": {
        "jsCode": "const workflowStaticData = $getWorkflowStaticData('global');\nif (!workflowStaticData.leads) {\n  workflowStaticData.leads = [];\n}\n\nconst lead = {\n  id: Date.now().toString(),\n  name: $input.item.json.name,\n  email: $input.item.json.email,\n  intent: $input.item.json.intent,\n  session_id: $input.item.json.session_id,\n  phone: $input.item.json.phone || '',\n  created_at: new Date().toISOString()\n};\n\nconst exists = workflowStaticData.leads.find(l => l.email === lead.email);\nif (!exists) {\n  workflowStaticData.leads.push(lead);\n}\n\nreturn { json: { success: true, lead_id: lead.id, is_duplicate: !!exists } };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        208,
        0
      ],
      "id": "node-savelead",
      "name": "Save Lead to Store"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "YOUR_SUPABASE_URL/rest/v1/leads",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "apikey",
              "value": "YOUR_SUPABASE_ANON_KEY"
            },
            {
              "name": "Authorization",
              "value": "Bearer YOUR_SUPABASE_ANON_KEY"
            },
            {
              "name": "Prefer",
              "value": "resolution=ignore-duplicates"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ name: $('Prepare Lead Data').item.json.name, email: $('Prepare Lead Data').item.json.email, phone: $('Prepare Lead Data').item.json.phone, intent: $('Prepare Lead Data').item.json.intent, session_id: $('Prepare Lead Data').item.json.session_id }) }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "node-supabase",
      "name": "Save to Supabase",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        448,
        0
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "url": "YOUR_CRM_FORM_URL",
        "options": {}
      },
      "id": "node-fetchcrm",
      "name": "Fetch CRM Form",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        688,
        0
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "// Extract CSRF token from the CRM form HTML\nconst html = $input.item.json.data || '';\nconst csrfMatch = html.match(/\"csrf_token_name\":\"([a-f0-9]+)\"/);\nconst csrfToken = csrfMatch ? csrfMatch[1] : '';\nconst key = 'YOUR_CRM_FORM_KEY';\n\nif (!csrfToken) {\n  throw new Error('Could not extract CSRF token from CRM form');\n}\n\nreturn {\n  json: {\n    csrf_token: csrfToken,\n    key: key,\n    name: $('Prepare Lead Data').item.json.name,\n    email: $('Prepare Lead Data').item.json.email,\n    phonenumber: $('Prepare Lead Data').item.json.phone,\n    description: 'AI Chatbot Lead | Intent: ' + $('Prepare Lead Data').item.json.intent + ' | Session: ' + $('Prepare Lead Data').item.json.session_id\n  }\n};"
      },
      "id": "node-csrf",
      "name": "Extract CSRF Token",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        928,
        0
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "YOUR_CRM_FORM_URL",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/x-www-form-urlencoded"
            },
            {
              "name": "Origin",
              "value": "YOUR_WEBSITE_ORIGIN"
            },
            {
              "name": "Referer",
              "value": "YOUR_CRM_FORM_URL"
            }
          ]
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "csrf_token_name",
              "value": "={{ $json.csrf_token }}"
            },
            {
              "name": "key",
              "value": "={{ $json.key }}"
            },
            {
              "name": "name",
              "value": "={{ $json.name }}"
            },
            {
              "name": "email",
              "value": "={{ $json.email }}"
            },
            {
              "name": "phonenumber",
              "value": "={{ $json.phonenumber }}"
            },
            {
              "name": "description",
              "value": "={{ $json.description }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "node-submitcrm",
      "name": "Submit to CRM",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1168,
        0
      ],
      "continueOnFail": true
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Validate Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Input": {
      "main": [
        [
          {
            "node": "Set Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Variables": {
      "main": [
        [
          {
            "node": "Recall Memory",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Recall Memory": {
      "main": [
        [
          {
            "node": "Build Request Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Request Body": {
      "main": [
        [
          {
            "node": "Gemini API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini API": {
      "main": [
        [
          {
            "node": "Gemini OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini OK?": {
      "main": [
        [
          {
            "node": "Extract Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Reply": {
      "main": [
        [
          {
            "node": "Save Memory",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Lead Detection Body",
            "type": "main",
            "index": 0
          },
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Lead Detection Body": {
      "main": [
        [
          {
            "node": "Lead Detection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lead Detection": {
      "main": [
        [
          {
            "node": "Parse Lead JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Lead JSON": {
      "main": [
        [
          {
            "node": "Has Lead?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Lead?": {
      "main": [
        [
          {
            "node": "Prepare Lead Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Lead Data": {
      "main": [
        [
          {
            "node": "Save Lead to Store",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Lead to Store": {
      "main": [
        [
          {
            "node": "Save to Supabase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Supabase": {
      "main": [
        [
          {
            "node": "Fetch CRM Form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch CRM Form": {
      "main": [
        [
          {
            "node": "Extract CSRF Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract CSRF Token": {
      "main": [
        [
          {
            "node": "Submit to CRM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    {
      "name": "AI"
    },
    {
      "name": "Chatbot"
    },
    {
      "name": "Lead Generation"
    }
  ]
}
Pro

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

How this works

This workflow powers an intelligent AI chatbot on your website, enabling real-time, context-aware conversations that enhance user engagement and provide instant support without human intervention. It's ideal for website owners, e-commerce businesses, or customer service teams seeking to automate responses to common queries, reducing response times and operational costs. The core step involves processing incoming messages via a webhook trigger, validating inputs, retrieving conversation history, and querying the Gemini API through an httpRequest node to generate personalised replies, ensuring seamless integration with your site's chat interface.

Use this workflow when building a scalable chatbot for handling routine customer interactions, such as product enquiries or troubleshooting, especially on high-traffic sites where quick replies boost satisfaction. Avoid it for complex, sensitive discussions requiring human oversight, like legal advice or financial transactions, or if your platform lacks webhook support. Common variations include swapping Gemini for another AI model via httpRequest adjustments, or adding database nodes for persistent memory beyond basic recall.

About this workflow

AI Website Chatbot — Main Handler. Uses httpRequest. Webhook trigger; 22 nodes.

Source: https://github.com/hasancoded/n8n-workflow-templates/blob/main/ai-website-chatbot.json — original creator credit. Request a take-down →

More General workflows → · Browse all categories →

Related workflows

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

General

GiveWP Donations to Beacon. Uses httpRequest, stopAndError. Webhook trigger; 43 nodes.

HTTP Request, Stop And Error
General

Remove Video Background & Compose on Custom Image Background with Google Drive. Uses httpRequest, googleDrive. Webhook trigger; 25 nodes.

HTTP Request, Google Drive
General

GYRA+ | 2. Receber Decisão → Rotear para CRM. Uses httpRequest. Webhook trigger; 15 nodes.

HTTP Request
General

Voice-Agent Orchestrator (Spec Example - import into n8n and adapt secrets). Uses httpRequest, respondToWebhook. Webhook trigger; 8 nodes.

HTTP Request
General

N8N Automation Templates. Uses httpRequest. Webhook trigger; 8 nodes.

HTTP Request