This workflow corresponds to n8n.io template #15304 — we link there as the canonical source.
This workflow follows the OpenAI → Slack 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 →
{
"id": "1kRvSB08K-gM_0-hSXcHQ",
"name": "Analyze customer support ticket sentiment and route to Slack channels",
"tags": [
{
"id": "5160a02758f54f13",
"name": "ai",
"createdAt": "2026-04-30T18:38:11.353194Z",
"updatedAt": "2026-04-30T18:38:11.353194Z"
},
{
"id": "274a391fee674268",
"name": "gpt-4",
"createdAt": "2026-04-30T18:38:11.353194Z",
"updatedAt": "2026-04-30T18:38:11.353194Z"
},
{
"id": "QWzGPDfiad0OMdvN",
"name": "openai",
"createdAt": "2026-04-22T22:17:12.459Z",
"updatedAt": "2026-04-22T22:17:12.459Z"
},
{
"id": "78e26cf7df1f4e52",
"name": "slack",
"createdAt": "2026-04-30T18:38:11.353194Z",
"updatedAt": "2026-04-30T18:38:11.353194Z"
},
{
"id": "loDvXh5Qq0snh5pT",
"name": "customer-support",
"createdAt": "2026-04-22T22:17:12.496Z",
"updatedAt": "2026-04-22T22:17:12.496Z"
},
{
"id": "FGrQ8vVq5xDBcJH4",
"name": "sentiment-analysis",
"createdAt": "2026-04-22T22:17:12.487Z",
"updatedAt": "2026-04-22T22:17:12.487Z"
},
{
"id": "3f8091ee9b744783",
"name": "automation",
"createdAt": "2026-04-30T18:38:11.353194Z",
"updatedAt": "2026-04-30T18:38:11.353194Z"
}
],
"nodes": [
{
"id": "634fcb35-e36f-47e8-b4ca-53acc96066d7",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1360,
-64
],
"parameters": {
"width": 480,
"height": 624,
"content": "## Analyze customer support ticket sentiment and route to Slack channels\n\n## How it works\n\n1. The workflow receives a support ticket via a webhook.\n2. It normalizes the ticket payload for processing.\n3. Sentiment is analyzed using the GPT-4o AI model.\n4. The AI response is parsed to determine routing.\n5. The message is routed to a specific Slack channel based on sentiment, and a routing result is returned.\n\n## Setup steps\n\n- [ ] Configure webhook URL to receive support tickets.\n- [ ] Set up OpenAI API credentials for sentiment analysis.\n- [ ] Connect Slack credentials to enable message posting to channels.\n\n## Customization\n\nThe routing logic can be adjusted based on different sentiment scores or additional criteria."
},
"typeVersion": 1
},
{
"id": "7cf71e92-5749-4091-a2a8-eac742b72e99",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-800,
96
],
"parameters": {
"color": 7,
"width": 400,
"height": 304,
"content": "## Receive and normalize\n\nHandles incoming support tickets and prepares them for analysis."
},
"typeVersion": 1
},
{
"id": "0f3b2ea2-1912-4658-aa42-bf407543f77b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
64
],
"parameters": {
"color": 7,
"width": 240,
"height": 320,
"content": "## Analyze sentiment\n\nUses AI to analyze customer sentiment from the support ticket."
},
"typeVersion": 1
},
{
"id": "1367d3d5-2a75-4b8b-b798-110dda9629d4",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
64,
96
],
"parameters": {
"color": 7,
"width": 416,
"height": 336,
"content": "## Parse and route\n\nParses the AI response and routes based on sentiment analysis."
},
"typeVersion": 1
},
{
"id": "8b69d01e-4962-4c7f-af4b-530f03a9bf61",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
512,
-64
],
"parameters": {
"color": 7,
"width": 416,
"height": 624,
"content": "## Post to Slack channels\n\nPosts the ticket to the appropriate Slack channel based on the anger score."
},
"typeVersion": 1
},
{
"id": "1665fb1a-e094-4de6-b9ab-1a963bfcc0fb",
"name": "When Ticket Received",
"type": "n8n-nodes-base.webhook",
"position": [
-752,
224
],
"parameters": {
"path": "support-ticket",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "02d910ef-92e6-4f39-8f7c-7b73c65c89a5",
"name": "Normalize Ticket Data",
"type": "n8n-nodes-base.code",
"position": [
-544,
224
],
"parameters": {
"jsCode": "// Normalize and validate incoming ticket payload\nconst body = $input.first().json.body || $input.first().json;\n\nconst ticketId = body.ticket_id || `TICKET-${Date.now()}`;\nconst customerName = body.customer_name || 'Unknown Customer';\nconst email = body.email || 'user@example.com';\nconst subject = body.subject || 'No Subject';\nconst message = body.message || body.text || body.content || '';\nconst priority = body.priority || 'normal';\nconst createdAt = body.created_at || new Date().toISOString();\n\nif (!message || message.trim().length === 0) {\n throw new Error('Ticket message body is empty or missing.');\n}\n\nreturn [{\n json: {\n ticket_id: ticketId,\n customer_name: customerName,\n email: email,\n subject: subject,\n message: message.trim(),\n priority: priority,\n created_at: createdAt,\n processed_at: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "6672218e-cd79-4ae9-97a9-b0cef65e0f20",
"name": "OpenAI Sentiment Analysis",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
-160,
224
],
"parameters": {
"resource": "chat"
},
"typeVersion": 1.8
},
{
"id": "278e210e-03cf-4eba-8d72-79cb8eeb27ce",
"name": "Parse Sentiment Output",
"type": "n8n-nodes-base.code",
"position": [
112,
224
],
"parameters": {
"jsCode": "// Parse AI JSON response and append routing decision\nconst ticketData = $('Normalize Ticket Data').first().json;\nconst aiContent = $input.first().json.message?.content\n || $input.first().json.choices?.[0]?.message?.content\n || '';\n\nlet analysis;\ntry {\n const clean = aiContent.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n analysis = JSON.parse(clean);\n} catch (e) {\n analysis = {\n anger_score: 5,\n sentiment_category: 'NEUTRAL',\n emotion_summary: 'AI response could not be parsed. Manual review required.',\n key_issues: ['Parse error'],\n urgency_level: 'MEDIUM',\n recommended_action: 'Review ticket manually'\n };\n}\n\nconst angerScore = Math.min(10, Math.max(0, parseInt(analysis.anger_score, 10) || 5));\n\nlet routing_channel, routing_reason;\nif (angerScore >= 8) {\n routing_channel = '#escalation';\n routing_reason = 'Score \u22658 \u2014 immediate escalation required';\n} else if (angerScore >= 4) {\n routing_channel = '#support';\n routing_reason = 'Score 4\u20137 \u2014 standard support handling';\n} else {\n routing_channel = '#feedback';\n routing_reason = 'Score \u22643 \u2014 feedback or general inquiry';\n}\n\nreturn [{\n json: {\n ...ticketData,\n anger_score: angerScore,\n sentiment_category: analysis.sentiment_category || 'NEUTRAL',\n emotion_summary: analysis.emotion_summary || '',\n key_issues: analysis.key_issues || [],\n urgency_level: analysis.urgency_level || 'MEDIUM',\n recommended_action: analysis.recommended_action || '',\n routing_channel,\n routing_reason,\n analyzed_at: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "5d665945-363d-4305-9b66-098ce47aa9ad",
"name": "Route by Sentiment Score",
"type": "n8n-nodes-base.switch",
"position": [
336,
224
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Escalation",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $json.anger_score }}",
"rightValue": 8
}
]
},
"renameOutput": true
},
{
"outputKey": "Support",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $json.anger_score }}",
"rightValue": 4
},
{
"operator": {
"type": "number",
"operation": "lte"
},
"leftValue": "={{ $json.anger_score }}",
"rightValue": 7
}
]
},
"renameOutput": true
},
{
"outputKey": "Feedback",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "number",
"operation": "lte"
},
"leftValue": "={{ $json.anger_score }}",
"rightValue": 3
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "bdac3d15-1858-4e12-909b-6b4324b1c4e8",
"name": "Send to Slack #escalation",
"type": "n8n-nodes-base.slack",
"position": [
560,
64
],
"parameters": {
"text": "=\ud83d\udea8 *URGENT ESCALATION REQUIRED*\n\n*Ticket:* `{{ $json.ticket_id }}` | *Score:* {{ $json.anger_score }}/10 \ud83d\udd34 | *Urgency:* {{ $json.urgency_level }}\n*Customer:* {{ $json.customer_name }} \u2014 {{ $json.email }}\n*Subject:* {{ $json.subject }}\n\n*Message:*\n> {{ $json.message }}\n\n*Sentiment:* {{ $json.sentiment_category }}\n*Summary:* {{ $json.emotion_summary }}\n*Issues:* {{ $json.key_issues.join(' / ') }}\n*Action:* {{ $json.recommended_action }}\n\n_Received: {{ $json.created_at }} | Analyzed: {{ $json.analyzed_at }}_",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "escalation"
},
"otherOptions": {
"includeLinkToWorkflow": false
},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "889e9ab6-c8c1-4d4d-baa2-cc7fd794fde0",
"name": "Send to Slack #support",
"type": "n8n-nodes-base.slack",
"position": [
560,
224
],
"parameters": {
"text": "=\u26a0\ufe0f *New Support Ticket*\n\n*Ticket:* `{{ $json.ticket_id }}` | *Score:* {{ $json.anger_score }}/10 \ud83d\udfe1 | *Urgency:* {{ $json.urgency_level }}\n*Customer:* {{ $json.customer_name }} \u2014 {{ $json.email }}\n*Subject:* {{ $json.subject }}\n\n*Message:*\n> {{ $json.message }}\n\n*Sentiment:* {{ $json.sentiment_category }}\n*Summary:* {{ $json.emotion_summary }}\n*Issues:* {{ $json.key_issues.join(' / ') }}\n*Action:* {{ $json.recommended_action }}\n\n_Received: {{ $json.created_at }} | Analyzed: {{ $json.analyzed_at }}_",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "support"
},
"otherOptions": {
"includeLinkToWorkflow": false
},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "652668fd-06e5-4737-b84e-3a33f3642b5d",
"name": "Send to Slack #feedback",
"type": "n8n-nodes-base.slack",
"position": [
560,
384
],
"parameters": {
"text": "=\ud83d\udcac *New Feedback / Inquiry*\n\n*Ticket:* `{{ $json.ticket_id }}` | *Score:* {{ $json.anger_score }}/10 \ud83d\udfe2 | *Urgency:* {{ $json.urgency_level }}\n*Customer:* {{ $json.customer_name }} \u2014 {{ $json.email }}\n*Subject:* {{ $json.subject }}\n\n*Message:*\n> {{ $json.message }}\n\n*Sentiment:* {{ $json.sentiment_category }}\n*Summary:* {{ $json.emotion_summary }}\n*Issues:* {{ $json.key_issues.join(' / ') }}\n*Action:* {{ $json.recommended_action }}\n\n_Received: {{ $json.created_at }} | Analyzed: {{ $json.analyzed_at }}_",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "feedback"
},
"otherOptions": {
"includeLinkToWorkflow": false
},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "0b88f173-9a26-40db-a73f-790edb454757",
"name": "Respond with Routing Outcome",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
784,
224
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, ticket_id: $('Parse Sentiment Output').first().json.ticket_id, anger_score: $('Parse Sentiment Output').first().json.anger_score, sentiment_category: $('Parse Sentiment Output').first().json.sentiment_category, routing_channel: $('Parse Sentiment Output').first().json.routing_channel }) }}"
},
"typeVersion": 1.1
}
],
"active": false,
"settings": {
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "f8ab54dd-d40c-40e2-aa3d-a8b849f703a4",
"connections": {
"When Ticket Received": {
"main": [
[
{
"node": "Normalize Ticket Data",
"type": "main",
"index": 0
}
]
]
},
"Normalize Ticket Data": {
"main": [
[
{
"node": "OpenAI Sentiment Analysis",
"type": "main",
"index": 0
}
]
]
},
"Parse Sentiment Output": {
"main": [
[
{
"node": "Route by Sentiment Score",
"type": "main",
"index": 0
}
]
]
},
"Send to Slack #support": {
"main": [
[
{
"node": "Respond with Routing Outcome",
"type": "main",
"index": 0
}
]
]
},
"Send to Slack #feedback": {
"main": [
[
{
"node": "Respond with Routing Outcome",
"type": "main",
"index": 0
}
]
]
},
"Route by Sentiment Score": {
"main": [
[
{
"node": "Send to Slack #escalation",
"type": "main",
"index": 0
}
],
[
{
"node": "Send to Slack #support",
"type": "main",
"index": 0
}
],
[
{
"node": "Send to Slack #feedback",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Sentiment Analysis": {
"main": [
[
{
"node": "Parse Sentiment Output",
"type": "main",
"index": 0
}
]
]
},
"Send to Slack #escalation": {
"main": [
[
{
"node": "Respond with Routing Outcome",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Customer support teams, customer experience managers, and operations teams that need to triage incoming support tickets in real time without manual classification. Ideal for SaaS companies, e-commerce businesses, and service providers handling high ticket volumes.
Source: https://n8n.io/workflows/15304/ — 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.
Venafi Presentation - Watch Video
Automatically detects missed Zoom demos booked via Calendly and triggers AI-powered follow-up sequences.
Pyragogy AI Village - Orchestrazione Master (Architettura Profonda V2). Uses start, postgres, openAi, emailSend. Webhook trigger; 36 nodes.
Pyragogy AI Village - Orchestrazione Master (Architettura Profonda V2). Uses start, postgres, openAi, emailSend. Webhook trigger; 35 nodes.
Imagine your recruitment process transformed into a sleek, efficient, AI-powered assembly line for talent. That's exactly what this system creates. It automates the heavy lifting, allowing your human