{ "name": "Flowise Chatflow API", "active": true, "settings": { "executionOrder": "v1" }, "nodes": [ { "parameters": { "httpMethod": "POST", "path": "flowise-chat", "options": {} }, "id": "flowise-webhook", "name": "Flowise Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 1.1, "position": [ 250, 300 ] }, { "parameters": { "jsCode": "const body = $json.body || $json;\nconst headers = $json.headers || {};\n\nconst expectedKey = $env.FLOWISE_API_KEY;\nconst providedKey = headers['x-api-key'] || headers['X-API-Key'];\n\n// Reject keys exceeding max length to prevent truncation attacks\nconst MAX_KEY_LENGTH = 128;\n\nfunction safeCompare(a, b) {\n // Reject keys exceeding max length first (length is not secret)\n if (a.length > MAX_KEY_LENGTH || b.length > MAX_KEY_LENGTH) {\n return false;\n }\n \n // Constant-time comparison (no padding, no branching on secret data)\n // If lengths differ, we still compare to avoid timing leaks, but will return false\n const len = Math.max(a.length, b.length);\n let result = a.length ^ b.length; // Non-zero if lengths differ\n \n for (let i = 0; i < len; i++) {\n // When index exceeds string length, use 0 (null byte) to avoid out-of-bounds\n const charA = i < a.length ? a.charCodeAt(i) : 0;\n const charB = i < b.length ? b.charCodeAt(i) : 0;\n result |= charA ^ charB;\n }\n return result === 0;\n}\n\nif (expectedKey && expectedKey.length > 0) {\n if (!providedKey) {\n return [{ json: { error: 'Authentication required', code: 401 } }];\n }\n if (!safeCompare(expectedKey, providedKey)) {\n return [{ json: { error: 'Invalid API key', code: 403 } }];\n }\n}\n\nconst chatflowId = body.chatflowId || $env.FLOWISE_DEFAULT_CHATFLOW_ID;\nif (!chatflowId) {\n return [{ json: { error: 'chatflowId required', code: 400 } }];\n}\n\n// Validate chatflowId format (UUID v4 or alphanumeric with hyphens only)\nconst validIdPattern = /^[a-zA-Z0-9-]+$/;\nif (!validIdPattern.test(chatflowId)) {\n return [{ json: { error: 'Invalid chatflowId format', code: 400 } }];\n}\n\nreturn [{\n json: {\n question: body.question || body.message || body.prompt || 'Hello',\n chatflowId: chatflowId,\n streaming: body.streaming || false,\n overrideConfig: body.overrideConfig || {}\n }\n}];" }, "id": "validate-request", "name": "Validate Request", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 450, 300 ] }, { "parameters": { "url": "={{ ($env.FLOWISE_INTERNAL_URL || 'http://flowise:3000') + '/api/v1/prediction/' + encodeURIComponent($json.chatflowId) }}", "method": "POST", "headers": { "list": [ { "name": "Content-Type", "value": "application/json" } ] }, "body": "={{ JSON.stringify({\n question: $json.question,\n streaming: $json.streaming,\n overrideConfig: $json.overrideConfig\n}) }}", "options": { "timeout": 120000, "continueOnFail": true } }, "id": "flowise-api", "name": "Flowise API", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 650, 300 ] }, { "parameters": { "jsCode": "const response = $json;\n\n// Handle HTTP errors (when continueOnFail catches them)\nif ($('Flowise API').last().error) {\n return [{\n json: {\n error: 'Upstream service error',\n code: 502\n }\n }];\n}\n\nif (response.error) {\n return [{\n json: {\n error: 'Request processing failed',\n code: response.code || 500\n }\n }];\n}\n\nreturn [{\n json: {\n text: response.text || response.response || 'No response',\n chatId: response.chatId,\n chatMessageId: response.chatMessageId,\n sourceDocuments: response.sourceDocuments || [],\n usedTools: response.usedTools || []\n }\n}];" }, "id": "format-response", "name": "Format Response", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 850, 300 ] }, { "parameters": { "respondWith": "json", "json": "={{ JSON.stringify($json) }}", "options": {} }, "id": "respond-to-webhook", "name": "Respond to Webhook", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [ 1050, 300 ] } ], "connections": { "Flowise Webhook": { "main": [ [ { "node": "Validate Request", "type": "main", "index": 0 } ] ] }, "Validate Request": { "main": [ [ { "node": "Flowise API", "type": "main", "index": 0 } ] ] }, "Flowise API": { "main": [ [ { "node": "Format Response", "type": "main", "index": 0 } ] ] }, "Format Response": { "main": [ [ { "node": "Respond to Webhook", "type": "main", "index": 0 } ] ] } }, "staticData": null, "tags": [] }