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 →
{
"name": "Chatbot Integration Workflow",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "chatbot",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
240,
300
]
},
{
"parameters": {
"functionCode": "// Extract and validate incoming data\nconst chatId = $input.first().json.body.chatId;\nconst message = $input.first().json.body.message;\nconst userId = $input.first().json.body.userId;\nconst userRole = $input.first().json.body.userRole;\n\n// Basic validation\nif (!chatId || !message || !userId) {\n throw new Error('Missing required fields: chatId, message, or userId');\n}\n\nif (userRole !== 'user') {\n throw new Error('Unauthorized: Invalid user role');\n}\n\nreturn [{\n json: {\n chatId,\n message,\n userId,\n timestamp: new Date().toISOString()\n }\n}];"
},
"id": "validate-input",
"name": "Validate Input",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
460,
300
]
},
{
"parameters": {
"url": "={{ $env.HASURA_GRAPHQL_ENDPOINT }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-hasura-admin-secret",
"value": "={{ $credentials.hasuraAdminSecret.headerValue }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "query",
"value": "query CheckChatOwnership($chatId: uuid!, $userId: uuid!) { chats(where: {id: {_eq: $chatId}, user_id: {_eq: $userId}}) { id user_id } }"
},
{
"name": "variables",
"value": "={{ { \"chatId\": $json.chatId, \"userId\": $json.userId } }}"
}
]
},
"options": {}
},
"id": "check-ownership",
"name": "Check Chat Ownership",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
680,
300
]
},
{
"parameters": {
"functionCode": "// Verify chat ownership\nconst response = $input.first().json;\nconst chats = response.data?.chats || [];\n\nif (chats.length === 0) {\n throw new Error('Unauthorized: User does not own this chat');\n}\n\nconst chat = chats[0];\nif (chat.user_id !== $('Validate Input').first().json.userId) {\n throw new Error('Unauthorized: Chat ownership mismatch');\n}\n\nreturn [{\n json: {\n chatId: $('Validate Input').first().json.chatId,\n message: $('Validate Input').first().json.message,\n userId: $('Validate Input').first().json.userId,\n authorized: true,\n timestamp: $('Validate Input').first().json.timestamp\n }\n}];"
},
"id": "verify-ownership",
"name": "Verify Ownership",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
900,
300
]
},
{
"parameters": {
"url": "https://openrouter.ai/api/v1/chat/completions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{ $credentials.openrouterApiKey.headerValue }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "model",
"value": "mistralai/mistral-7b-instruct:free"
},
{
"name": "messages",
"value": "={{ [ { \"role\": \"system\", \"content\": \"You are a helpful AI assistant. Provide clear, concise, and helpful responses. Keep responses under 500 characters when possible.\" }, { \"role\": \"user\", \"content\": $json.message } ] }}"
},
{
"name": "max_tokens",
"value": 500
},
{
"name": "temperature",
"value": 0.7
}
]
},
"options": {}
},
"id": "call-openrouter",
"name": "Call OpenRouter API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1120,
300
]
},
{
"parameters": {
"functionCode": "// Extract AI response\nconst response = $input.first().json;\nconst assistantMessage = response.choices?.[0]?.message?.content || 'Sorry, I could not generate a response.';\n\n// Clean up the response\nconst cleanMessage = assistantMessage.trim();\n\nreturn [{\n json: {\n chatId: $('Verify Ownership').first().json.chatId,\n userId: $('Verify Ownership').first().json.userId,\n assistantMessage: cleanMessage,\n originalResponse: response,\n timestamp: new Date().toISOString()\n }\n}];"
},
"id": "process-response",
"name": "Process AI Response",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1340,
300
]
},
{
"parameters": {
"url": "={{ $env.HASURA_GRAPHQL_ENDPOINT }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-hasura-admin-secret",
"value": "={{ $credentials.hasuraAdminSecret.headerValue }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "query",
"value": "mutation InsertAssistantMessage($chatId: uuid!, $userId: uuid!, $content: String!) { insert_messages_one(object: {chat_id: $chatId, user_id: $userId, content: $content, role: \"assistant\"}) { id content role created_at } }"
},
{
"name": "variables",
"value": "={{ { \"chatId\": $json.chatId, \"userId\": $json.userId, \"content\": $json.assistantMessage } }}"
}
]
},
"options": {}
},
"id": "save-to-database",
"name": "Save Response to Database",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1560,
300
]
},
{
"parameters": {
"functionCode": "// Format final response for Hasura Action\nconst assistantMessage = $('Process AI Response').first().json.assistantMessage;\nconst saveResult = $input.first().json;\n\n// Check if message was saved successfully\nconst success = saveResult.data?.insert_messages_one?.id ? true : false;\nconst error = success ? null : 'Failed to save message to database';\n\nreturn [{\n json: {\n response: assistantMessage,\n success: success,\n error: error,\n messageId: saveResult.data?.insert_messages_one?.id || null,\n timestamp: new Date().toISOString()\n }\n}];"
},
"id": "format-response",
"name": "Format Final Response",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1780,
300
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}"
},
"id": "webhook-response",
"name": "Send Response",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
2000,
300
]
},
{
"parameters": {
"functionCode": "// Global error handler\nconst error = $input.first().error || {};\nconst errorMessage = error.message || 'An unknown error occurred';\n\nconsole.error('Workflow error:', error);\n\nreturn [{\n json: {\n response: null,\n success: false,\n error: errorMessage,\n timestamp: new Date().toISOString()\n }\n}];"
},
"id": "error-handler",
"name": "Error Handler",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1000,
500
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}"
},
"id": "error-response",
"name": "Send Error Response",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
1220,
500
]
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Validate Input",
"type": "main",
"index": 0
}
]
]
},
"Validate Input": {
"main": [
[
{
"node": "Check Chat Ownership",
"type": "main",
"index": 0
}
]
]
},
"Check Chat Ownership": {
"main": [
[
{
"node": "Verify Ownership",
"type": "main",
"index": 0
}
]
]
},
"Verify Ownership": {
"main": [
[
{
"node": "Call OpenRouter API",
"type": "main",
"index": 0
}
]
]
},
"Call OpenRouter API": {
"main": [
[
{
"node": "Process AI Response",
"type": "main",
"index": 0
}
]
]
},
"Process AI Response": {
"main": [
[
{
"node": "Save Response to Database",
"type": "main",
"index": 0
}
]
]
},
"Save Response to Database": {
"main": [
[
{
"node": "Format Final Response",
"type": "main",
"index": 0
}
]
]
},
"Format Final Response": {
"main": [
[
{
"node": "Send Response",
"type": "main",
"index": 0
}
]
]
},
"Error Handler": {
"main": [
[
{
"node": "Send Error Response",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"errorWorkflow": {
"callerPolicy": "workflowSettings"
},
"timezone": "UTC",
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all"
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow enables seamless integration of a custom chatbot into your messaging platform, allowing users to receive intelligent, context-aware responses without building everything from scratch. It's ideal for developers or small teams managing customer support or internal queries, saving hours on API handling and validation. The key step involves validating user ownership via an HTTP request before routing the query to the OpenRouter API for processing, ensuring secure and accurate replies.
Use this workflow when you need a lightweight, webhook-triggered chatbot that integrates with external AI services like OpenRouter for quick setups in apps or websites. Avoid it for high-volume enterprise needs requiring advanced AI nodes or complex state management, as it's best for simple, ownership-verified interactions. Common variations include swapping OpenRouter for another API provider or adding database storage for conversation history.
About this workflow
Chatbot Integration Workflow. Uses httpRequest. Webhook trigger; 11 nodes.
Source: https://github.com/DEVRAJSONI01/assignment1/blob/e143b57898dda3a77c3cba5a1b2869cb0acbd647/n8n/workflow-export.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Portfolio Orchestrator. Uses httpRequest. Webhook trigger; 59 nodes.
jump-section: Comment Fix Pipeline. Uses httpRequest. Webhook trigger; 24 nodes.
GitHub Issues Router (Linear / Jira / ClickUp). Uses stickyNote, httpRequest, respondToWebhook. Webhook trigger; 23 nodes.
Form to CRM Lead Router (Pipedrive / HubSpot / Salesforce). Uses stickyNote, httpRequest, respondToWebhook. Webhook trigger; 22 nodes.
Calendly to CRM Sync (Pipedrive / HubSpot / Salesforce). Uses stickyNote, httpRequest, respondToWebhook. Webhook trigger; 22 nodes.