This workflow corresponds to n8n.io template #16893 — we link there as the canonical source.
This workflow follows the Agent → 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 →
{
"id": "IOrcccOQVsTVUMHU",
"name": "Summarize Slack threads into Jira tickets with approval",
"tags": [
{
"id": "ZWvxxvFcQ1uDlnvE",
"name": "Published",
"createdAt": "2026-07-01T15:26:02.670Z",
"updatedAt": "2026-07-01T15:26:02.670Z"
}
],
"nodes": [
{
"id": "c0795814-d743-42fe-84d8-d388dd00cdf5",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
3024,
1616
],
"parameters": {
"width": 560,
"height": 1000,
"content": "## Summarize Slack threads into Jira tickets with approval\n\n### How it works\n\n1. A Slack slash command triggers the workflow and immediately responds to acknowledge receipt.\n2. Configuration values are set, then the Slack thread history is fetched via HTTP request.\n3. The thread is formatted and sent to an AI agent to draft a Jira ticket summary.\n4. The draft is parsed and posted back to Slack as a preview with Approve/Cancel buttons.\n5. When a user clicks a button, the interaction handler parses the payload and routes to either create the Jira issue or cancel.\n6. The outcome (success or cancellation) is posted back to the Slack thread.\n\n### Setup steps\n\n- [ ] Configure Slack credentials for the slash command trigger and HTTP requests\n- [ ] Set up Jira credentials for the Create an issue node\n- [ ] Configure OpenRouter API key for the AI Ticket Drafter agent\n- [ ] Set the Slack slash command URL in Slack app settings to point to the webhook URL\n- [ ] Configure the JIRA_PROJECT_KEY and JIRA_ISSUE_TYPE in the Config node\n- [ ] Ensure the Slack bot has permissions to read channel history and post messages\n\n### Customization\n\nAdjust MESSAGE_COUNT in Config to control how many thread messages are fetched. Modify the AI agent prompt in the Ticket Drafter Agent to change ticket drafting style. Update button labels or add more options in Post Preview with Buttons."
},
"typeVersion": 1
},
{
"id": "8ff62775-458a-4951-87e0-317c814e9fe6",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
3632,
1616
],
"parameters": {
"color": 7,
"width": 480,
"height": 496,
"content": "## Receive Slack slash command\n\nEntry point triggered by a Slack slash command. Immediately acknowledges the command to prevent timeout."
},
"typeVersion": 1
},
{
"id": "fa43a577-d1b0-4056-b787-b10f2d97386b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
4144,
1616
],
"parameters": {
"color": 7,
"width": 736,
"height": 496,
"content": "## Fetch and format thread\n\nSets configuration constants, retrieves the Slack conversation history via API, and formats the thread for AI processing."
},
"typeVersion": 1
},
{
"id": "0d2b5358-b6ce-4774-9edc-51014a1d8a97",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
4912,
1616
],
"parameters": {
"color": 7,
"width": 928,
"height": 496,
"content": "## Draft ticket with AI\n\nAI agent drafts a structured Jira ticket from the formatted thread. The draft is parsed and posted to Slack as an interactive preview with approval buttons."
},
"typeVersion": 1
},
{
"id": "03bd24ef-5396-4bdb-8f74-d0610bfc32f6",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3632,
2160
],
"parameters": {
"color": 7,
"width": 544,
"height": 592,
"content": "## Handle button interaction\n\nReceives the Slack button click interaction, acknowledges it, and parses the payload to determine whether the user approved or cancelled."
},
"typeVersion": 1
},
{
"id": "304aff7e-73ee-4be6-bd3f-537df22de7ce",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
4208,
2160
],
"parameters": {
"color": 7,
"width": 832,
"height": 592,
"content": "## Create Jira issue or cancel\n\nConditional branch: on approval, creates the Jira issue and posts success confirmation to Slack; on cancellation, posts a cancellation notice."
},
"typeVersion": 1
},
{
"id": "1bd9538f-a861-4d71-8a53-14f802507cf7",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
3920,
1920
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "{\n \"response_type\": \"ephemeral\",\n \"text\": \"\u23f3 Je pr\u00e9pare un brouillon de ticket \u00e0 partir des derniers messages...\"\n}"
},
"typeVersion": 1.5
},
{
"id": "83391863-888a-461d-adae-6f4a891c8504",
"name": "Config",
"type": "n8n-nodes-base.set",
"position": [
4192,
1776
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "889df2b2-9017-437c-bdb7-ca1c43fa8400",
"name": "MESSAGE_COUNT",
"type": "number",
"value": 5
},
{
"id": "f5e54454-404b-48e1-88a8-e920eb34c7ca",
"name": "JIRA_PROJECT_KEY",
"type": "string",
"value": "YOUR JIRA PROJECT KEY"
},
{
"id": "ee987fbc-8bca-416d-bb99-e6b20613a645",
"name": "JIRA_ISSUE_TYPE",
"type": "string",
"value": "Task"
},
{
"id": "dac1e680-74a0-4e50-8281-2434b37369ee",
"name": "SLACK_CHANNEL_ID",
"type": "string",
"value": "={{ $('Slack Slash Command Trigger').item.json.body.channel_id }}"
},
{
"id": "da7d34d9-f5cf-4b4a-a1fd-38fab16eec32",
"name": "SLACK_USER_ID",
"type": "string",
"value": "={{ $('Slack Slash Command Trigger').item.json.body.user_id }}"
},
{
"id": "82dc13c1-98a3-474c-a3f5-83730debcdab",
"name": "SLACK_RESPONSE_URL",
"type": "string",
"value": "={{ $('Slack Slash Command Trigger').item.json.body.response_url }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "830d7c31-996b-490f-abc4-73ef9f87c149",
"name": "Slack Slash Command Trigger",
"type": "n8n-nodes-base.webhook",
"position": [
3696,
1776
],
"parameters": {
"path": "slack-ticket",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "21a22a4f-3c45-4a66-8dda-600cac6101ef",
"name": "Format Thread Code",
"type": "n8n-nodes-base.code",
"position": [
4688,
1776
],
"parameters": {
"jsCode": "const data = $input.first().json;\n\n// Guard: Slack API error\nif (data.ok === false) {\n return [{\n json: {\n error: true,\n error_reason: data.error || \"Unknown Slack API error\",\n formatted_thread: \"\"\n }\n }];\n}\n\nconst messages = data.messages ?? [];\n\n// Slack returns newest-first \u2192 reverse to chronological order\nconst ordered = [...messages].reverse();\n\n// Filter out noise: bot messages, join/leave, empty text\nconst cleaned = ordered.filter(m =>\n m.type === \"message\" &&\n !m.subtype && // removes channel_join, bot_message, etc.\n m.text &&\n m.text.trim().length > 0\n);\n\n// Guard: nothing usable\nif (cleaned.length === 0) {\n return [{\n json: {\n error: true,\n error_reason: \"No usable messages found in the channel.\",\n formatted_thread: \"\"\n }\n }];\n}\n\n// Build a clean conversation transcript\nconst transcript = cleaned\n .map(m => `<@${m.user}>: ${m.text}`)\n .join(\"\\n\");\n\nreturn [{\n json: {\n error: false,\n message_count: cleaned.length,\n formatted_thread: transcript\n }\n}];"
},
"typeVersion": 2
},
{
"id": "7178079f-634d-4a9b-bfc8-c02df2438328",
"name": "Parse Draft Code",
"type": "n8n-nodes-base.code",
"position": [
5408,
1776
],
"parameters": {
"jsCode": "const response = $input.first().json.output ?? $input.first().json.text ?? \"\";\n\nconst cleaned = response\n .replace(/^```json\\s*/i, \"\")\n .replace(/^```\\s*/i, \"\")\n .replace(/\\s*```$/i, \"\")\n .trim();\n\nconst config = $('Config').first().json;\n\ntry {\n const draft = JSON.parse(cleaned);\n return [{\n json: {\n ...draft,\n // Carry Slack context forward for the preview + button payload\n slack_channel_id: config.SLACK_CHANNEL_ID,\n slack_user_id: config.SLACK_USER_ID,\n slack_response_url: config.SLACK_RESPONSE_URL,\n jira_project_key: config.JIRA_PROJECT_KEY\n }\n }];\n} catch (error) {\n return [{\n json: {\n error: true,\n actionable: false,\n title: \"\",\n description: `Draft parsing failed: ${error.message}`,\n slack_response_url: config.SLACK_RESPONSE_URL\n }\n }];\n}"
},
"typeVersion": 2
},
{
"id": "c398560b-a4dc-4cf4-b6ac-e50cd915bfe5",
"name": "Post Preview with Buttons",
"type": "n8n-nodes-base.httpRequest",
"position": [
5632,
1776
],
"parameters": {
"url": "={{ $json.slack_response_url }}",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({\n response_type: \"ephemeral\",\n replace_original: true,\n blocks: [\n {\n type: \"header\",\n text: { type: \"plain_text\", text: \"\ud83c\udfab Aper\u00e7u du ticket\" }\n },\n {\n type: \"section\",\n fields: [\n { type: \"mrkdwn\", text: `*Titre:*\\n${$json.title}` },\n { type: \"mrkdwn\", text: `*Type:*\\n${$json.type}` },\n { type: \"mrkdwn\", text: `*Priorit\u00e9:*\\n${$json.priority}` },\n { type: \"mrkdwn\", text: `*Projet:*\\n${$json.jira_project_key}` }\n ]\n },\n {\n type: \"section\",\n text: { type: \"mrkdwn\", text: `*Description:*\\n${$json.description}` }\n },\n {\n type: \"section\",\n text: { type: \"mrkdwn\", text: `*Crit\u00e8res d'acceptance:*\\n${($json.acceptance_criteria || []).map(c => `\u2022 ${c}`).join(\"\\n\") || \"_Aucun_\"}` }\n },\n {\n type: \"actions\",\n elements: [\n {\n type: \"button\",\n style: \"primary\",\n text: { type: \"plain_text\", text: \"\u2705 Cr\u00e9er le ticket\" },\n action_id: \"create_ticket\",\n value: JSON.stringify({\n title: $json.title,\n type: $json.type,\n priority: $json.priority,\n description: $json.description,\n acceptance_criteria: $json.acceptance_criteria,\n project: $json.jira_project_key,\n channel: $json.slack_channel_id\n }).slice(0, 1990)\n },\n {\n type: \"button\",\n style: \"danger\",\n text: { type: \"plain_text\", text: \"\u274c Annuler\" },\n action_id: \"cancel_ticket\",\n value: \"cancel\"\n }\n ]\n }\n ]\n}) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "slackApi"
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "0d871a4f-c29b-4aa2-8fd0-dfe7f551a1cc",
"name": "Interaction Handler",
"type": "n8n-nodes-base.webhook",
"position": [
3712,
2416
],
"parameters": {
"path": "slack-interaction",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "2a8e7dcb-f472-415f-a77d-4209b87872d3",
"name": "Respond to Interaction",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
3952,
2320
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "{\n \"response_type\": \"ephemeral\",\n \"replace_original\": true,\n \"text\": \"\u23f3 Cr\u00e9ation du ticket en cours...\"\n}"
},
"typeVersion": 1.5
},
{
"id": "961f3d23-4d36-4194-8aa8-ce8e0c50f2b3",
"name": "Parse Payload Code",
"type": "n8n-nodes-base.code",
"position": [
3952,
2592
],
"parameters": {
"jsCode": "// Slack sends the interaction as a URL-encoded 'payload' field\n// whose value is a JSON string \u2192 parse it\nconst raw = $input.first().json.body?.payload ?? $input.first().json.payload;\n\nlet payload;\ntry {\n payload = typeof raw === \"string\" ? JSON.parse(raw) : raw;\n} catch (error) {\n return [{\n json: {\n error: true,\n error_reason: `Failed to parse Slack payload: ${error.message}`\n }\n }];\n}\n\nconst action = payload.actions?.[0] ?? {};\nconst actionId = action.action_id ?? \"\";\nconst responseUrl = payload.response_url ?? \"\";\nconst clickedBy = payload.user?.id ?? \"\";\n\n// If \"Create\" was clicked, decode the ticket JSON stored in the button value\nlet ticket = null;\nif (actionId === \"create_ticket\") {\n try {\n ticket = JSON.parse(action.value);\n } catch (error) {\n return [{\n json: {\n error: true,\n action_id: actionId,\n response_url: responseUrl,\n error_reason: `Failed to parse ticket from button: ${error.message}`\n }\n }];\n }\n}\n\nreturn [{\n json: {\n error: false,\n action_id: actionId, // \"create_ticket\" | \"cancel_ticket\"\n response_url: responseUrl,\n clicked_by: clickedBy,\n ticket: ticket // full ticket object, or null if cancelled\n }\n}];"
},
"typeVersion": 2
},
{
"id": "f27b74f8-aef8-485b-9e64-188593bbbf03",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
4256,
2480
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5220c440-4933-482d-a64e-8afd8569a2a3",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.action_id }}",
"rightValue": "create_ticket"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "ac37bd1b-d226-44ad-9d31-07e3427ac11c",
"name": "Create an issue",
"type": "n8n-nodes-base.jira",
"position": [
4528,
2368
],
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"value": "10034",
"cachedResultName": "n8n_test_sprint"
},
"summary": "={{ $json.ticket.title }}",
"issueType": {
"__rl": true,
"mode": "list",
"value": "10048",
"cachedResultName": "T\u00e2che"
},
"additionalFields": {
"description": "={{ $json.ticket.description }} \n\n*Crit\u00e8res d'acceptance:* \n{{ ($json.ticket.acceptance_criteria || []).map(c => `- ${c}`).join('\\n') }} \n\n_Cr\u00e9\u00e9 automatiquement depuis Slack._"
}
},
"credentials": {
"jiraSoftwareCloudApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "e42fcb09-2626-4607-98dd-5e284d38a7b1",
"name": "Ticket Drafter Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
5072,
1776
],
"parameters": {
"text": "=Turn this Slack conversation into a Jira ticket draft.\n\n<conversation>\n{{ $json.formatted_thread }}\n</conversation>\n\nReturn the ticket draft JSON.",
"options": {
"systemMessage": "You are a product manager assistant that turns messy Slack conversations \ninto clean, actionable Jira tickets.\n\nYou receive a channel conversation transcript. Your job is to extract the \ncore issue, request, or idea being discussed and format it as a well-structured \nticket that a developer could pick up without needing to read the original thread.\n\nGuidelines:\n- Identify the single most important actionable item in the conversation.\n- Write a clear, specific title (action-oriented, not vague).\n- Write a description that captures the context, the problem, and the desired outcome.\n- Extract acceptance criteria if the conversation implies any.\n- Infer a sensible ticket type: \"Bug\" if something is broken, \"Story\" if it's a \n user-facing feature, \"Task\" for everything else.\n- Infer priority from urgency signals in the conversation (words like \"urgent\", \n \"blocking\", \"prod down\" \u2192 High; casual ideas \u2192 Low; default Medium).\n- If the conversation is too vague or has no actionable item, set \"actionable\": false \n and explain why in the description.\n- Never invent details that aren't supported by the conversation.\n\nOutput a JSON object with this exact structure:\n{\n \"actionable\": true,\n \"title\": \"concise, specific ticket title\",\n \"type\": \"Bug\" | \"Story\" | \"Task\",\n \"priority\": \"High\" | \"Medium\" | \"Low\",\n \"description\": \"clear description with context and desired outcome\",\n \"acceptance_criteria\": [\"criterion 1\", \"criterion 2\"],\n \"source_summary\": \"1-sentence summary of what was discussed in Slack\"\n}\n\nReturn only the JSON object. No preamble, no markdown fences."
},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "2c033f6c-5b44-4f15-896b-6cc94049426e",
"name": "Open AI Ticket Drafter",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
5008,
1952
],
"parameters": {
"model": "openai/gpt-oss-20b:free",
"options": {}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "e71c27c9-31ba-48cc-8a13-f8d26db842c5",
"name": "Update Slack - Success",
"type": "n8n-nodes-base.httpRequest",
"position": [
4768,
2368
],
"parameters": {
"url": "={{ $('Parse Payload Code').item.json.response_url }}",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({\n response_type: \"ephemeral\",\n replace_original: true,\n blocks: [\n {\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: `\u2705 *Ticket cr\u00e9\u00e9*`\n }\n }\n ]\n}) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.4
},
{
"id": "75d9aa4f-377f-4af2-bfa3-07066f629e53",
"name": "Update Slack - Cancelled",
"type": "n8n-nodes-base.httpRequest",
"position": [
4544,
2576
],
"parameters": {
"url": "={{ $('Parse Payload Code').item.json.response_url }}",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({\n \"response_type\": \"ephemeral\",\n \"replace_original\": true,\n \"text\": \"\u274c Cr\u00e9ation du ticket annul\u00e9e.\"\n}) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.4
},
{
"id": "d0c81d66-15d4-45e8-94ed-d6e4d0e516fb",
"name": "Get conversation history",
"type": "n8n-nodes-base.httpRequest",
"position": [
4448,
1776
],
"parameters": {
"url": "https://slack.com/api/conversations.history",
"options": {},
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "channel",
"value": "={{ $('Config').item.json.SLACK_CHANNEL_ID }}"
},
{
"name": "limit",
"value": "={{ $('Config').item.json.MESSAGE_COUNT }}"
}
]
},
"nodeCredentialType": "slackApi"
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "93779181-5465-4603-9f54-360d51cba5f7",
"connections": {
"If": {
"main": [
[
{
"node": "Create an issue",
"type": "main",
"index": 0
}
],
[
{
"node": "Update Slack - Cancelled",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Get conversation history",
"type": "main",
"index": 0
}
]
]
},
"Create an issue": {
"main": [
[
{
"node": "Update Slack - Success",
"type": "main",
"index": 0
}
]
]
},
"Parse Draft Code": {
"main": [
[
{
"node": "Post Preview with Buttons",
"type": "main",
"index": 0
}
]
]
},
"Format Thread Code": {
"main": [
[
{
"node": "Ticket Drafter Agent",
"type": "main",
"index": 0
}
]
]
},
"Parse Payload Code": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Interaction Handler": {
"main": [
[
{
"node": "Respond to Interaction",
"type": "main",
"index": 0
},
{
"node": "Parse Payload Code",
"type": "main",
"index": 0
}
]
]
},
"Ticket Drafter Agent": {
"main": [
[
{
"node": "Parse Draft Code",
"type": "main",
"index": 0
}
]
]
},
"Open AI Ticket Drafter": {
"ai_languageModel": [
[
{
"node": "Ticket Drafter Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Get conversation history": {
"main": [
[
{
"node": "Format Thread Code",
"type": "main",
"index": 0
}
]
]
},
"Slack Slash Command Trigger": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
},
{
"node": "Config",
"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.
jiraSoftwareCloudApiopenRouterApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow turns a Slack slash-command request into an AI-drafted Jira ticket preview, then creates the Jira issue only after a user approves it in Slack using interactive buttons. Receives a Slack slash command via a webhook and immediately responds with an ephemeral…
Source: https://n8n.io/workflows/16893/ — 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.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCalculator. Webhook trigger; 63 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCalculator. Webhook trigger; 63 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCalculator. Webhook trigger; 63 nodes.
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCode. Webhook trigger; 62 nodes.