AutomationFlowsAI & RAG › Route Customer Support Requests to AI Specialists with Openrouter

Route Customer Support Requests to AI Specialists with Openrouter

ByElvis Sarvia @elvissaravia on n8n.io

This workflow receives customer support requests via a webhook, normalizes the payload, and uses an OpenRouter-powered orchestrator agent to route the request to a Billing, Technical, or Account specialist agent. It then returns a structured JSON reply to the caller or escalates…

Webhook trigger★★★★☆ complexityAI-powered15 nodesAgentAgent ToolOpenRouter Chat
AI & RAG Trigger: Webhook Nodes: 15 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Agent → Agenttool 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": "DEpx5XVZjsY9MUT0",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Template 1: Multi-Agent Customer Router (AI Agent Tool)",
  "tags": [],
  "nodes": [
    {
      "id": "ef6b3b90-6424-43bd-bd99-d7610178cb2e",
      "name": "Webhook - Customer Request",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -96,
        304
      ],
      "parameters": {
        "path": "customer-request-aat",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "f62aadb9-94d9-4aed-8c22-f8cecbe15e6b",
      "name": "Normalize Request",
      "type": "n8n-nodes-base.code",
      "position": [
        128,
        304
      ],
      "parameters": {
        "jsCode": "// Accept both webhook-wrapped body and direct JSON input\nconst raw = $input.first().json;\nconst src = (raw && typeof raw.body === 'object' && raw.body !== null) ? raw.body : raw;\nreturn {\n  json: {\n    requestId: src.requestId || 'REQ-' + Date.now(),\n    customerId: String(src.customerId || '').trim(),\n    customerTier: src.customerTier || 'standard',\n    message: String(src.message || src.text || '').trim().substring(0, 3000),\n    email: String(src.email || '').toLowerCase().trim(),\n    timestamp: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "dc19758e-2521-43d9-a589-7e017b1a62e1",
      "name": "Orchestrator Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        640,
        304
      ],
      "parameters": {
        "text": "=Customer tier: {{ $json.customerTier }}\nCustomer request: {{ $json.message }}",
        "options": {
          "systemMessage": "You are a customer operations orchestrator for a multi-agent support system. You have three specialist tools available:\n\n- Billing_Specialist: Use for refunds, payment issues, invoice questions, subscription billing problems.\n- Technical_Specialist: Use for API errors, integration issues, product bugs, and feature usage questions.\n- Account_Specialist: Use for plan upgrades, cancellations, account settings, and team management.\n\nWorkflow:\n1. Read the customer request carefully.\n2. Call exactly one specialist tool, passing the full customer request to it.\n3. Use the specialist's response to craft your final reply.\n4. Return ONLY valid JSON in this exact format:\n\n{\n  \"specialist\": \"billing|technical|account\",\n  \"responseToCustomer\": \"the professional message to send to the customer\",\n  \"actionTaken\": \"summary of what the specialist did\",\n  \"reasoning\": \"why this specialist was chosen\"\n}\n\nIf the request is ambiguous or outside all three domains, return:\n{\n  \"specialist\": \"none\",\n  \"responseToCustomer\": \"Thank you for reaching out. Your request has been forwarded to a team member who will follow up shortly.\",\n  \"actionTaken\": \"escalated_to_human\",\n  \"reasoning\": \"request did not fit any specialist scope\"\n}"
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "a9ab89c5-6cae-462d-9a71-9f666b3b5385",
      "name": "Billing_Specialist",
      "type": "@n8n/n8n-nodes-langchain.agentTool",
      "position": [
        480,
        528
      ],
      "parameters": {
        "text": "={{ $fromAI('customer_request', 'The customer request or question this specialist should handle', 'string') }}",
        "options": {
          "systemMessage": "You are the Billing Specialist agent. You handle refund requests, payment issues, invoice questions, and subscription billing problems. You know the refund policy, payment retry logic, and standard resolution steps. If you would take a system action (issue a refund, update a payment method, regenerate an invoice), describe it clearly. In production this specialist would have real tools connected (billing API, Stripe, etc.). Respond with a clear resolution and the professional message the orchestrator should relay to the customer."
        },
        "toolDescription": "Use for refund requests, payment issues, invoice questions, and subscription billing problems. Pass the full customer request as the customer_request parameter."
      },
      "typeVersion": 3
    },
    {
      "id": "6cfe98e1-38c6-4da0-b3eb-1e78efe287b8",
      "name": "Technical_Specialist",
      "type": "@n8n/n8n-nodes-langchain.agentTool",
      "position": [
        768,
        528
      ],
      "parameters": {
        "text": "={{ $fromAI('customer_request', 'The customer request or question this specialist should handle', 'string') }}",
        "options": {
          "systemMessage": "You are the Technical Support Specialist agent. You handle API errors, integration issues, product bugs, and feature usage questions. You are familiar with the product documentation, common error codes, and troubleshooting steps. If you would look up documentation or check system status, describe that action. In production this specialist would have real tools connected (vector store for docs, status page API, etc.). Respond with a clear resolution and the professional message the orchestrator should relay to the customer."
        },
        "toolDescription": "Use for API errors, integration issues, product bugs, and feature usage questions. Pass the full customer request as the customer_request parameter."
      },
      "typeVersion": 3
    },
    {
      "id": "ce4e7c17-d7f2-4edb-a7b1-4de6b60879ae",
      "name": "Account_Specialist",
      "type": "@n8n/n8n-nodes-langchain.agentTool",
      "position": [
        1056,
        528
      ],
      "parameters": {
        "text": "={{ $fromAI('customer_request', 'The customer request or question this specialist should handle', 'string') }}",
        "options": {
          "systemMessage": "You are the Account Management Specialist agent. You handle plan upgrades, cancellations, account settings, and team management. You know the plan tiers, pricing, cancellation policy, and how to update account configuration. If you would take an action (upgrade plan, add team members, process a cancellation), describe it clearly. In production this specialist would have real tools connected (CRM, admin API, etc.). Respond with a clear resolution and the professional message the orchestrator should relay to the customer."
        },
        "toolDescription": "Use for plan upgrades, cancellations, account settings, and team management. Pass the full customer request as the customer_request parameter."
      },
      "typeVersion": 3
    },
    {
      "id": "211ce069-16c5-4ebd-8eec-65be053bf096",
      "name": "Parse Final Response",
      "type": "n8n-nodes-base.code",
      "position": [
        1424,
        304
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json;\nlet parsed;\ntry {\n  const text = typeof raw.output === 'string' ? raw.output : JSON.stringify(raw.output);\n  const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n  parsed = JSON.parse(jsonMatch ? jsonMatch[0] : text);\n  if (!parsed.responseToCustomer) throw new Error('Missing responseToCustomer');\n  parsed.success = parsed.specialist && parsed.specialist !== 'none';\n} catch(e) {\n  parsed = {\n    success: false,\n    specialist: 'none',\n    responseToCustomer: 'Thank you for reaching out. Your request has been forwarded to a team member who will follow up shortly.',\n    actionTaken: 'escalated_to_human',\n    reasoning: 'orchestrator_parse_failure',\n    error: { type: 'parse_error', message: e.message, lastOutput: typeof raw.output === 'string' ? raw.output.substring(0, 500) : JSON.stringify(raw.output).substring(0, 500) }\n  };\n}\n\nreturn {\n  json: {\n    ...parsed,\n    requestId: $('Normalize Request').first().json.requestId\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "4907588a-4423-4313-8b14-5d43d90337fe",
      "name": "Orchestrator Succeeded?",
      "type": "n8n-nodes-base.if",
      "position": [
        1648,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "orchestrator-success",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.success }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4d87a042-fabb-4f4e-adac-596c6fc29b36",
      "name": "Escalate to Human",
      "type": "n8n-nodes-base.code",
      "position": [
        1872,
        384
      ],
      "parameters": {
        "jsCode": "const req = $input.first().json;\nreturn {\n  json: {\n    requestId: req.requestId || 'unknown',\n    action: 'escalated_to_human',\n    reason: req.reasoning || req.error?.type || 'orchestrator_failure',\n    orchestratorError: req.error || null,\n    humanQueueChannel: '#support-escalations',\n    note: 'This request requires human review. In production, route to Slack, Teams, or an internal queue using the Send and Wait for Response pattern from the Human Oversight post.',\n    responseToCustomer: req.responseToCustomer || 'Thank you for reaching out. Your request has been forwarded to a team member who will follow up shortly.'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e3cbb4fb-271d-4fe8-b2c8-1bced52b05b2",
      "name": "Respond to Customer",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        2096,
        304
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ requestId: $json.requestId, success: $json.success === true, specialist: $json.specialist, response: $json.responseToCustomer, action: $json.actionTaken || $json.action, reasoning: $json.reasoning }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "aea82660-ed86-48e5-97c5-13d1e99d7815",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -784,
        48
      ],
      "parameters": {
        "width": 620,
        "height": 708,
        "content": "## Multi-Agent Customer Router (AI Agent Tool)\n\n### How it works\nAn orchestrator agent reasons about an incoming customer request and delegates to the right specialist via the AI Agent Tool pattern:\n1. **Intake** (Deterministic): Webhook receives the customer request. Code node normalizes the payload (requestId, tier, message).\n2. **Orchestrator Agent** (AI): Reads the request, decides which specialist to call based on tool descriptions, invokes the matching specialist as a tool.\n3. **Specialist tools** (AI, 3): Billing, Technical, and Account specialists each have their own Chat Model, system prompt, and domain scope. Only the selected one runs.\n4. **Parse Final Response** (Deterministic): Extracts the structured resolution the orchestrator returns.\n5. **Decision + Outcome**: Successful resolutions flow to Respond to Customer. Unresolved or low-confidence cases escalate to human review.\n\n### Setup\n- Connect your **LLM credentials** to each Chat Model sub-node (orchestrator + 3 specialists)\n- Copy the Webhook test URL and POST a sample customer request with `requestId`, `customerId`, `customerTier`, `message`\n- Start with a clear billing question to trigger the Billing specialist\n\n### Customization\n- Swap in a cheaper model for the orchestrator and stronger ones for specialists, or vice versa\n- Edit each specialist's tool description and system prompt to match your actual domains\n- Add or remove specialists by changing the orchestrator's tool list\n\nThis template is a learning companion to the Production AI Playbook, a series that explores strategies, shares best practices, and provides practical examples for building reliable AI systems in n8n."
      },
      "typeVersion": 1
    },
    {
      "id": "b2f32195-730c-46aa-8dc6-0ee70d70b01f",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        352,
        528
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8e26b05a-7ef9-42c6-ac82-a0db5db568a8",
      "name": "OpenRouter Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        464,
        736
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "80fd60f5-513a-4401-8e0f-0b883c018d22",
      "name": "OpenRouter Chat Model2",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        784,
        656
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e383ea4d-db21-47c9-bbc0-50aa91ffb83f",
      "name": "OpenRouter Chat Model3",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        1072,
        656
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "26c2642d-9cde-4425-b101-2e96b4b087d7",
  "connections": {
    "Escalate to Human": {
      "main": [
        [
          {
            "node": "Respond to Customer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Request": {
      "main": [
        [
          {
            "node": "Orchestrator Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Account_Specialist": {
      "ai_tool": [
        [
          {
            "node": "Orchestrator Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Billing_Specialist": {
      "ai_tool": [
        [
          {
            "node": "Orchestrator Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Orchestrator Agent": {
      "main": [
        [
          {
            "node": "Parse Final Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Final Response": {
      "main": [
        [
          {
            "node": "Orchestrator Succeeded?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Technical_Specialist": {
      "ai_tool": [
        [
          {
            "node": "Orchestrator Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Orchestrator Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Billing_Specialist",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model2": {
      "ai_languageModel": [
        [
          {
            "node": "Technical_Specialist",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model3": {
      "ai_languageModel": [
        [
          {
            "node": "Account_Specialist",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Orchestrator Succeeded?": {
      "main": [
        [
          {
            "node": "Respond to Customer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Escalate to Human",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Customer Request": {
      "main": [
        [
          {
            "node": "Normalize Request",
            "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

This workflow receives customer support requests via a webhook, normalizes the payload, and uses an OpenRouter-powered orchestrator agent to route the request to a Billing, Technical, or Account specialist agent. It then returns a structured JSON reply to the caller or escalates…

Source: https://n8n.io/workflows/15949/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

When a new vendor ticket is created in Jira, this workflow automatically performs a comprehensive security due-diligence investigation and posts the findings back as a Jira comment plus a Slack notifi

Agent, OpenRouter Chat, Slack +2
AI & RAG

Are you drowning in daily operational chaos, desperately trying to juggle sales, projects, content, and client communication? Imagine an AI brain that handles it all, freeing you to lead your business

Telegram Trigger, Telegram, OpenAI +13
AI & RAG

The AI-Powered Shopify SEO Content Automation is an enterprise-grade workflow that transforms product content creation for e-commerce stores. This sophisticated multi-agent system integrates GPT-4o, C

Perplexity Tool, Memory Buffer Window, Agent +15
AI & RAG

Who is this for? Agencies, consultants, and service providers who conduct discovery calls and need to quickly turn conversations into professional proposals.

Tool Think, Tool Calculator, Agent Tool +18
AI & RAG

🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCalculator. Webhook trigger; 63 nodes.

HTTP Request, Postgres, Postgres Tool +9