AutomationFlowsWeb Scraping › Build a Multichannel Customer Support AI Assistant with Chatwoot & Openrouter

Build a Multichannel Customer Support AI Assistant with Chatwoot & Openrouter

ByGeorge Zargaryan @zrgrn on n8n.io

Receive new messages via a webhook. Retrieve conversation history. Process the message history into a format suitable for an LLM. Demonstrate an AI Assistant processing a user's query. Send the AI Assistant's response back to Chatwoot.

Webhook trigger★★★★☆ complexityAI-powered13 nodesHTTP RequestOpenRouter ChatChain Llm
Web Scraping Trigger: Webhook Nodes: 13 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #8260 — we link there as the canonical source.

This workflow follows the Chainllm → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "gfA9Xdug8uza14hb",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Multichannel AI Assistant Demo for Chatwoot",
  "tags": [],
  "nodes": [
    {
      "id": "your-id",
      "name": "Chatwoot Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        48,
        -48
      ],
      "parameters": {
        "path": "your-path",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "2aed8dcb-506a-4ede-a708-06b8c3087461",
      "name": "Squize Webhook Data",
      "type": "n8n-nodes-base.set",
      "position": [
        272,
        -48
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={\n  \"data\": {\n    \"inbox_id\": \"{{ $json.body.inbox.id }}\",\n    \"inbox_title\": \"{{ $json.body.inbox.name }}\",\n    \"conv_id\": \"{{ $json.body.conversation.id }}\",\n    \"channel_type\": \"{{ $json.body.conversation.channel }}\",\n    \"message_id\": \"{{ $json.body.id }}\",\n    \"message_content\": \"{{ $json.body.content.replace(/\\r?\\n/g, '\\\\n') }}\",\n    \"message_type\": \"{{ $json.body.message_type }}\",\n    \"private\": \"{{ $json.body.private }}\",\n    \"account_id\": \"{{ $json.body.account.id }}\",\n    \"account_name\": \"{{ $json.body.account.name }}\",\n    \"content_type\": \"{{ $json.body.content_type }}\",\n    \"created_at\": \"{{ $json.body.created_at }}\",\n    \"event\": \"{{ $json.body.event }}\",\n    \"conversation_labels\": {{ $json.body.conversation.labels || [] }},\n    \"attachments\": {{ $json.body.attachments || [] }},\n    \"meta\": {{ $json.body.conversation.meta || null }}\n  }\n} "
      },
      "typeVersion": 3.4
    },
    {
      "id": "c8691100-44a3-47b1-9724-662da1875ad2",
      "name": "Check If Incoming Message",
      "type": "n8n-nodes-base.if",
      "position": [
        496,
        -48
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e60a5d22-65b5-4500-b59b-cdf70b19c945",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data.message_type }}",
              "rightValue": "incoming"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "04d24443-a918-42a3-b96b-ae0cf9d93082",
      "name": "Load Chatwoot Conversation History",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1024,
        -208
      ],
      "parameters": {
        "url": "=https://yourchatwooturl.com/api/v1/accounts/{{ $('Squize Webhook Data').item.json.data.account_id }}/conversations/{{ $('Squize Webhook Data').item.json.data.conv_id }}/messages",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "api_access_token",
              "value": "YOUR_ACCESS_TOKEN"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "3d5acbb7-88ee-4758-9130-378986cc6713",
      "name": "Process Loaded History",
      "type": "n8n-nodes-base.code",
      "position": [
        1248,
        -208
      ],
      "parameters": {
        "jsCode": "// Initialize the default output structure to be returned in case of error or empty input.\nconst defaultOutput = {\n    \"conversation_text\": \"\"\n};\n\ntry {\n    // --- INPUTS ---\n    // Assuming the primary message payload is from the first input item.\n    const messages = $('Load Chatwoot Conversation History').first().json.payload;\n\n    // Ensure 'messages' is a valid array and messages.length > 0 before proceeding.\n    if (Array.isArray(messages) && messages.length > 0) {\n\n        // Map over each message to transform it into the desired format.\n        const fullHistory = messages.map(msg => {\n            // RULE 1: Determine the role based on the sender type.\n            const role = (msg.sender && msg.sender.type === 'contact') ? 'user' : 'assistant';\n\n            // RULE 2: Replace '/start' content with 'Hello!'.\n            // FIX: Default null content to an empty string to ensure safe concatenation.\n            let content = msg.content || '';\n\n            if (content === '/start') {\n                content = 'Hello!';\n            }\n\n            return {\n                role,\n                content\n            };\n        });\n\n        // --- TASK 2: Remove Messages with Empty Content ---\n        // Filter the generated history to exclude any messages where the content is empty or just whitespace.\n        const filteredHistory = fullHistory.filter(msg => msg.content && msg.content.trim() !== '');\n\n        // --- TASK 3: Create a single text string from the entire history ---\n        // This now uses the entire filteredHistory instead of a slice.\n        const conversationText = filteredHistory\n            .map(msg => `[${msg.role.toUpperCase()}] : ${msg.content}`)\n            .join(\", \\n\");\n\n        // Return the final string in the expected n8n JSON format.\n        return [{\n            json: {\n                \"conversation_text\": conversationText\n            }\n        }];\n\n    }\n\n    // If the input was invalid or empty, return the default empty structure.\n    return [{\n        json: defaultOutput\n    }];\n\n} catch (error) {\n    // RULE 3: If any error occurs during execution, return the default empty structure.\n    // This ensures the workflow doesn't fail.\n    console.error(\"Error processing history:\", error.message);\n    return [{\n        json: defaultOutput\n    }];\n}\n\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f8992fc5-23c5-41f7-9e42-66a6a24dd07e",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        1904,
        96
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0ba89367-f064-4a1d-9442-debb80b00228",
      "name": "Chatwoot Assistant",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1952,
        -48
      ],
      "parameters": {
        "text": "=`conversation history`: `{{ $json.conversation_text }}` ",
        "batching": {},
        "messages": {
          "messageValues": [
            {
              "message": "=# ROLE & GOAL\nYou are a friendly and helpful AI assistant for Chatwoot Integration. Your primary goal is to answer user questions about Chatwoot's APIs accurately and concisely.\n\n# PRIMARY DIRECTIVE\nYou MUST provide assistance based *only* on the information provided in the `KNOWLEDGE BASE` section below. Do not use any external knowledge. If the `KNOWLEDGE BASE` does not contain the necessary information to answer a question, you must refer the user to the official documentation.\n\n# KNOWLEDGE BASE\n---\nWhether you\u2019re building custom workflows for your support team, integrating Chatwoot into your product, or managing users across installations, our APIs provide the flexibility and power to help you do more with Chatwoot.\nChatwoot provides three categories of APIs, each designed with a specific use case in mind:\n- **Application APIs:** For account-level automation and agent-facing integrations.\n- **Client APIs:** For building custom chat interfaces for end-users\n- **Platform APIs:** For managing and administering installations at scale\n\n**Application APIs** are designed for interacting with a Chatwoot account from an agent/admin perspective. Use them to build internal tools, automate workflows, or perform bulk operations like data import/export.\n- **Authentication:** Requires a user `access_token`, which can be generated from Profile Settings after logging into your Chatwoot account.\n- **Availability:** Supported on both Cloud and Self-hosted Chatwoot installations.\n\n**Client APIs** are intended for building custom messaging experiences over Chatwoot. If you\u2019re not using the native website widget or want to embed chat in your mobile app, these APIs are the way to go.\n- **Authentication:** Uses `inbox_identifier` (from Settings \u2192 Configuration in API inboxes) and `contact_identifier` (returned when creating a contact).\n- **Availability:** Supported on both Cloud and Self-hosted Chatwoot installations.\n\n**Platform APIs** are used to manage Chatwoot installations at the admin level. These APIs allow you to control users, roles, and accounts, or sync data from external authentication systems.\n- **Note:** Platform APIs cannot access accounts or users created via the Chatwoot UI, or by other API keys. They can only access objects created by the specific platform API key used for authentication.\n- **Authentication:** Requires an `access_token` generated by a Platform App, which can be created in the Super Admin Console.\n- **Availability:** Available on Self-hosted / Managed Hosting Chatwoot installations only.\n---\n\n# OPERATING RULES\n1.  **Persona:** Your tone is friendly and helpful, but your answers must be compact and concise. Get straight to the point.\n2.  **Knowledge Constraint:** Adhere strictly to the `KNOWLEDGE BASE`. Do not invent or infer information.\n3.  **Complete Fallback:** If the user's question cannot be answered *at all* from the `KNOWLEDGE BASE`, your only response should be to refer them to the official documentation at `https://developers.chatwoot.com/introduction`.\n4.  **Partial Answer Rule:** If you can answer part of the question from the `KNOWLEDGE BASE`, provide that partial answer first, then state that for more details, the user should consult the official documentation.\n5.  **Focus on Last Message:** The `conversation_history` is for context only. Your main focus is always the last user message.\n6.  **Final Output:** Your response must ONLY be the final, user-facing message. Do not include any explanations, preambles, or self-commentary.\n\n# INPUT & OUTPUT FORMAT\n-   **INPUT:** You will receive the conversation history as a single string.\n-   **OUTPUT:** You will provide only the direct response to the user.\n\n# EXAMPLES\n---\n**EXAMPLE 1: Information is in the Knowledge Base**\n\n**INPUT:**\n`conversation_history`: `[ASSISTANT]: Sure, what else can I help you with? \\n [USER]: Hi, does Chatwoot provide API?`\n\n**ASSISTANT RESPONSE:**\nYes, Chatwoot provides three categories of APIs: Application APIs for account-level automation, Client APIs for building custom chat interfaces, and Platform APIs for managing installations. Which one are you interested in?\n---\n**EXAMPLE 2: Information is NOT in the Knowledge Base**\n\n**INPUT:**\n`conversation_history`: `[ASSISTANT]: The Client API is available on both cloud and self-hosted installations. \\n [USER]: What are the rate limits for the Client API?`\n\n**ASSISTANT RESPONSE:**\nI do not have information on specific API rate limits. For technical details like that, please consult the official documentation at `https://developers.chatwoot.com/introduction`.\n---"
            }
          ]
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "7fe05d5a-b8ac-4bab-84b7-cae0dee8c9f1",
      "name": "Send Message",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2576,
        -48
      ],
      "parameters": {
        "url": "=https://yourchatwooturl.com/api/v1/accounts/{{ $('Squize Webhook Data').item.json.data.account_id }}/conversations/{{ $('Squize Webhook Data').item.json.data.conv_id }}/messages",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "content",
              "value": "={{ $json.text }}"
            },
            {
              "name": "message_type",
              "value": "outgoing"
            },
            {
              "name": "private",
              "value": "false"
            },
            {
              "name": "content_type",
              "value": "text"
            },
            {
              "name": "content_attributes",
              "value": "{}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "api_access_token",
              "value": "YOUR_ACCESS_TOKEN"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e60f34f5-e688-44b6-8d96-b323cfdb0bf2",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -640,
        -560
      ],
      "parameters": {
        "width": 476,
        "height": 1344,
        "content": "## Multichannel AI Assistant Demo for Chatwoot\n\n### This simple n8n template demonstrates a Chatwoot integration that can:\n\n* Receive new messages via a webhook.\n* Retrieve conversation history.\n* Process the message history into a format suitable for an LLM.\n* Demonstrate an AI Assistant processing a user's query.\n* Send the AI Assistant's response back to Chatwoot.\n\n**Use Case:**\nIf you have multiple communication channels with your clients (e.g., Telegram, Instagram, WhatsApp, Facebook) integrated with Chatwoot, you can use this template as a starting point to build more sophisticated and tailored AI solutions that cover all channels at once.\n\n### How it works\n* A webhook receives the `message created` event from Chatwoot.\n* The webhook data is then filtered to keep only the necessary information for a cleaner workflow.\n* The workflow checks if the message is \"incoming.\" This is crucial to prevent the assistant from replying to its own messages and creating endless loops.\n* The conversation history is retrieved from Chatwoot via an API call using the HTTP Request node. This allows the assistant's interaction to be more natural and continuous without needing to store conversation history locally.\n* A simple AI Assistant processes the conversation history and generates a response to the user based on its built-in knowledge base (see the prompt in the assistant node).\n* The final HTTP Request node sends the AI-generated response back to the appropriate Chatwoot conversation.\n\n### How to Use\n1.  In Chatwoot, go to Settings \u2192 Integrations \u2192 Webhooks and add your n8n webhook URL. Be sure to select the `message created` event.\n2.  In the HTTP Request nodes, replace the placeholder values:\n    * `https://yourchatwooturl.com`\n    * `api_access_token`\n    You can find these values on your Chatwoot super admin page.\n3.  The LLM node is configured to use OpenRouter. Add your OpenRouter credentials, or replace the node with your preferred LLM provider.\n\n### Requirements\n* An API key for OpenRouter or credentials for your preferred LLM provider.\n* A Chatwoot account with at least one integrated channel and super admin access to obtain the `api_access_token`.\n\n### Need Help Building Something More?\nContact me on Telegram: @ninesfork\n\nHappy Hacking!"
      },
      "typeVersion": 1
    },
    {
      "id": "679990cb-6239-4f4f-87fc-b94f0535f334",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 648,
        "height": 476,
        "content": "## Receiving and Preparing Data\n\nThis part of the workflow **receives data** from the webhook trigger. This data is immediately filtered to remove irrelevant fields and keep only the necessary information. A crucial final check verifies that the message type is `incoming`, which is essential for preventing the system from replying to its own messages and creating infinite loops. \ud83d\udce1"
      },
      "typeVersion": 1
    },
    {
      "id": "48c75b75-beaa-43c8-b758-72b0f614641c",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        -528
      ],
      "parameters": {
        "color": 7,
        "width": 536,
        "height": 508,
        "content": "## Load and Process Conversation History\n\nThis part of the workflow **retrieves the full conversation history** from the API using an `HTTP Request` node. A `Code` node then extracts the `payload` array from the response and processes it into a single, clean string.\n\nThe output is formatted specifically for the language model:\n`\"[USER]: text\\n[ASSISTANT]: text\\n...\"`\n\nThis plain-text format is more efficient and prevents sending complex or \"noisy\" JSON data directly to the LLM. \ud83e\udd16"
      },
      "typeVersion": 1
    },
    {
      "id": "c8752288-6a02-41a1-9443-b07f5bb4044c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1824,
        -432
      ],
      "parameters": {
        "color": 7,
        "width": 440,
        "height": 684,
        "content": "## Simple AI Assistant\n\nThis demo provides a basic example of how to structure `system` and `user` messages in `Basic LLM Node`. This method ensures the AI's response stays within the context of a provided knowledge base.\n\nFor more dynamic and powerful systems, you may need to augment this workflow with more advanced approaches, such as:\n\n* **Retrieval-Augmented Generation (RAG):** Integrate a vector database to pull in relevant, real-time information.\n* **Advanced Prompting Techniques:** Apply methods like Chain-of-Thought or few-shot examples to improve the AI's reasoning and response quality. \ud83e\udde0"
      },
      "typeVersion": 1
    },
    {
      "id": "dd325687-7207-44f2-be87-3c88d3499f09",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2464,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 312,
        "height": 492,
        "content": "## Send AI Response to Chatwoot\n\nThis final `HTTP Request` node **sends the AI-generated response** back to the correct conversation in Chatwoot.\n\nTo do this, it uses the `account_id` and `conv_id` values that were retrieved and isolated by a previous node in the workflow. \ud83d\ude80"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "0f04c96e-2827-4d12-b25a-d3d0aab1d01a",
  "connections": {
    "Chatwoot Webhook": {
      "main": [
        [
          {
            "node": "Squize Webhook Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chatwoot Assistant": {
      "main": [
        [
          {
            "node": "Send Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Squize Webhook Data": {
      "main": [
        [
          {
            "node": "Check If Incoming Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Chatwoot Assistant",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Process Loaded History": {
      "main": [
        [
          {
            "node": "Chatwoot Assistant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Incoming Message": {
      "main": [
        [
          {
            "node": "Load Chatwoot Conversation History",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Load Chatwoot Conversation History": {
      "main": [
        [
          {
            "node": "Process Loaded History",
            "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

Receive new messages via a webhook. Retrieve conversation history. Process the message history into a format suitable for an LLM. Demonstrate an AI Assistant processing a user's query. Send the AI Assistant's response back to Chatwoot.

Source: https://n8n.io/workflows/8260/ — 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

Animal advocates & campaigners who want a weekly briefing on animal-related bills with clear, actionable steps—no manual research needed.

OpenRouter Chat, HTTP Request, Information Extractor +3
Web Scraping

Automated SEO Audit in n8n – Your All-in-One Website Optimization Tool!

HTTP Request, Html Extract, Email Send +2
Web Scraping

This workflow is built for one core purpose: to maximize the return on your reading time. It turns your passive consumption of articles and highlights into an active system for generating original con

HTTP Request, Chain Llm, OpenRouter Chat
Web Scraping

Automate your n8n community job board monitoring with this intelligent workflow that scrapes, analyzes, and delivers opportunities straight to your inbox. Perfect for freelancers, agencies, and develo

HTTP Request, Output Parser Structured, OpenRouter Chat +2
Web Scraping

Transform your customer support workflow with intelligent ticket classification. This automation leverages AI to automatically categorize incoming support tickets in Zoho Desk, reducing manual work an

OpenRouter Chat, HTTP Request, Chain Llm