This workflow corresponds to n8n.io template #8156 — we link there as the canonical source.
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": "2DxalJmssGiuQC9h",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Automate Jira Ticket Creation from Your Streamlit App",
"tags": [],
"nodes": [
{
"id": "26e7793d-e192-4075-8873-4c97fa2fe616",
"name": "Webhook streamlit",
"type": "n8n-nodes-base.webhook",
"position": [
-352,
16
],
"parameters": {
"path": "your path here",
"options": {},
"httpMethod": "POST",
"responseMode": "lastNode"
},
"typeVersion": 2.1
},
{
"id": "e3d42140-21c8-414e-92bb-9b370db03cd8",
"name": "jira response",
"type": "n8n-nodes-base.set",
"position": [
1488,
0
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={{$json}}"
},
"typeVersion": 3.4
},
{
"id": "99fc511f-63a6-4bae-bd76-c71e7d8cd41f",
"name": "Jira HTTP request",
"type": "n8n-nodes-base.httpRequest",
"position": [
1248,
0
],
"parameters": {
"body": "={{ JSON.stringify($json) }}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "raw",
"sendHeaders": true,
"rawContentType": "application/json",
"headerParameters": {
"parameters": [
{
"name": "=Content-Type",
"value": "=application/json"
},
{
"name": "Accept",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "ed82c023-8052-4431-81cc-1474cdd41d7d",
"name": "raw data from streamlit",
"type": "n8n-nodes-base.set",
"position": [
80,
16
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={{ $json }}"
},
"typeVersion": 3.4
},
{
"id": "561cad41-bb3f-4c79-8040-dd8eea591ecf",
"name": "Process streamlit data",
"type": "n8n-nodes-base.code",
"position": [
304,
16
],
"parameters": {
"jsCode": "const input = $json;\n\n// Journal to debug\nconsole.log(\"=== DATA RECEIVED ===\");\nconsole.log(JSON.stringify(input, null, 2));\n\n// stop if request empty\nconst quickCheck = input.body?.ticket?.summary || input.ticket?.summary || input.summary;\nif (!quickCheck || quickCheck.toString().trim() === \"\") {\n console.log(\"ARR\u00caT : Empty request detected\");\n return [{ json: { blocked: true, reason: \"empty_request\" } }];\n}\n\n// check: stop if no useful data\nif (!input || Object.keys(input).length === 0) {\n console.log(\"ARR\u00caT : No data received\");\n return [{ json: { blocked: true, reason: \"no_data\" } }];\n}\n\n// Extraction\nconst getValue = (obj, keys, defaultValue = null) => {\n for (const key of keys) {\n if (obj && obj[key] !== undefined && obj[key] !== null && obj[key] !== \"\") {\n return obj[key];\n }\n }\n return defaultValue;\n};\n\n// In case if data are in input.ticket\nconst ticketData = input.ticket || input.body?.ticket || input;\n\n// Extract with strict validation\nconst projectKey = getValue(ticketData, ['projectKey', 'project'], \"TES\");\nconst summary = getValue(ticketData, ['summary', 'title', 'name']);\nconst type = getValue(ticketData, ['type', 'issuetype'], \"Task\");\nconst description = getValue(ticketData, ['description', 'desc']);\nconst priority = getValue(ticketData, ['priority', 'severity']);\nconst storyPoints = getValue(ticketData, ['story_points', 'storyPoints', 'points']);\nconst assignee = getValue(ticketData, ['assignee', 'assignedTo', 'owner']);\n\n// critical validation, stop if summary is empty\nif (!summary || summary.trim() === \"\" || summary === \"Ticket without title\") {\n console.log(\"ARR\u00caT : Empty Summary or by default detected\");\n return [{ json: { blocked: true, reason: \"no_summary\" } }];\n}\n\n// critical validation, stop if description is empty\nif (!description || description.trim() === \"\" || description === \"Aucune description fournie\") {\n console.log(\"STOP : Empty Description or by default detected\");\n return [{ json: { blocked: true, reason: \"no_description\" } }];\n}\n\nconsole.log(\"=== Values Validated ===\");\nconsole.log(JSON.stringify({\n projectKey,\n summary,\n type,\n description,\n priority,\n storyPoints,\n assignee\n}, null, 2));\n\n// payload only if data are ok\nconst payload = {\n fields: {\n project: { key: projectKey.toString().toUpperCase() },\n summary: summary.toString().trim(),\n issuetype: { name: type.toString() },\n description: {\n type: \"doc\",\n version: 1,\n content: [{\n type: \"paragraph\",\n content: [{ type: \"text\", text: description.toString().trim() }]\n }]\n }\n }\n};\n\n\nif (priority && priority !== \"null\" && priority.trim() !== \"\") {\n payload.fields.priority = { name: priority.toString().trim() };\n}\n\n// Story points : ID can change depending on Jira\nif (storyPoints !== null && storyPoints !== undefined && !isNaN(parseInt(storyPoints))) {\npayload.fields.customfield_10016 = parseInt(storyPoints);\n}\n\n// Assignee deleted : needs accountId in Jira Cloud, not 'name'\n// if (assignee && assignee.trim() !== \"\") {\n// payload.fields.assignee = { name: assignee.toString().trim() };\n// }\n\nconsole.log(\"=== FINAL PAYLOAD VALIDATED ===\");\nconsole.log(JSON.stringify(payload, null, 2));\n\nreturn [{ json: payload }];"
},
"typeVersion": 2
},
{
"id": "08a64f5f-a115-492b-a74d-729a26e91964",
"name": "Processed data",
"type": "n8n-nodes-base.set",
"position": [
928,
0
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={{ $json }}"
},
"typeVersion": 3.4
},
{
"id": "04a874db-e6f5-45b0-9c24-c1353fbe82c4",
"name": "Result",
"type": "n8n-nodes-base.code",
"position": [
1744,
0
],
"parameters": {
"jsCode": "return [{ json: {\n ok: true,\n jiraKey: $json.key,\n url: `https://YOURJIRAURL.atlassian.net/browse/${$json.key}`\n}}];\n"
},
"typeVersion": 2
},
{
"id": "f73b22ee-e14e-4d0c-a638-86c79c79b540",
"name": "anti double",
"type": "n8n-nodes-base.code",
"position": [
-144,
16
],
"parameters": {
"jsCode": "// Block empty request\nconst hasValidSummary = $json.body?.ticket?.summary || $json.ticket?.summary || $json.summary;\n\nif (!hasValidSummary || hasValidSummary.trim() === \"\") {\n console.log(\"Empty request detected - blocked\");\n return [{ json: { blocked: true, reason: \"empty_request\" } }];\n}\n\n// allow valid data\nreturn items;"
},
"typeVersion": 2
},
{
"id": "eb9ab21e-f39e-4f79-b7e1-7ea092e2d8e7",
"name": "blocked request",
"type": "n8n-nodes-base.code",
"position": [
624,
416
],
"parameters": {
"jsCode": "return [{ json: { ok: false, message: \"Empty request blocked\" } }];"
},
"typeVersion": 2
},
{
"id": "1d1a8b2d-6cbf-4844-9ca3-112d1786ae53",
"name": "if for doubles",
"type": "n8n-nodes-base.if",
"position": [
496,
16
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b785d1c2-020d-4456-87cc-9d2389a30635",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.blocked }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "25ce6d92-a06e-46fc-9cd4-61a3a598b9ab",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-976,
-704
],
"parameters": {
"color": 3,
"width": 304,
"height": 176,
"content": "## Required\n\n\n- Streamlit creation ticket app\n- Jira account"
},
"typeVersion": 1
},
{
"id": "f81324d8-fd8e-485a-8398-72768c64ff09",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-624,
-704
],
"parameters": {
"color": 2,
"width": 416,
"height": 1264,
"content": "## 1) Trigger & intake (Streamlit \u2192 n8n)\n\nPurpose: receive the ticket from the app and hand it to the workflow.\n\nNodes\n\nWebhook streamlit \u2013 receives action=create_ticket and a ticket object (id, projectKey, type, summary, description, priority, story_points, due_date, labels\u2026).\n\nraw data from streamlit (Set) \u2013 optional pass-through to visualize the raw payload in executions.\n\nSetup\n\nWebhook \u2192 Response mode: lastNode (so the final node\u2019s response goes back to Streamlit).\n\nUse the Production URL in the app (avoid the Test URL in production).\n\nDon\u2019t run the Jira HTTP node manually; always trigger through the Webhook."
},
"typeVersion": 1
},
{
"id": "5e0bc57b-223c-46fe-bd90-5682ef4c5e67",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1152,
-704
],
"parameters": {
"color": 4,
"width": 464,
"height": 1264,
"content": "## 4) Create issue in Jira\n\nPurpose: call Jira REST API and create the real ticket.\n\nNodes\n\nProcessed data (Set) \u2013 passes through the fields object so you can inspect it.\n\nJira HTTP request (HTTP Request)\n\nMethod: POST\n\nURL: https://<your-domain>.atlassian.net/rest/api/3/issue\n\nAuth: Jira Software Cloud API (email + API token)\n\nHeaders: Content-Type: application/json (Accept optional)\n\nBody: Raw JSON = { \"fields\": \u2026 } from the previous node (not a stringified \u201c[object Object]\u201d).\n\nExpected output\n\nJira returns JSON with the new issue key (e.g., TES-123).\n"
},
"typeVersion": 1
},
{
"id": "5175079e-7ddc-46f1-bf93-532421c2e16a",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
816,
-704
],
"parameters": {
"color": 5,
"width": 304,
"height": 1264,
"content": "## 3) Normalize & build Jira payload\n\nPurpose: transform app fields into a valid Jira JSON.\n\nNodes\n\nProcess streamlit data (Code) \u2013 outputs a clean fields object:\n\nproject.key (uppercase from projectKey)\n\nissuetype.name (Task/Story/Bug/Epic)\n\nsummary\n\ndescription in Atlassian document format (doc \u2192 paragraph \u2192 text)\n\nOptional: priority.name, duedate (YYYY-MM-DD), labels (array)\n\nOptional: customfield_10016 for Story Points (or your instance\u2019s ID)\n\nNotes\n\nDon\u2019t set assignee.name in Jira Cloud; use assignee.accountId only if you have it."
},
"typeVersion": 1
},
{
"id": "953b841a-8814-4047-bce8-a8644d266005",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-176,
-704
],
"parameters": {
"color": 6,
"width": 944,
"height": 1264,
"content": "## 2) De-dup guard & branching (count/IF)\n\nPurpose: prevent empty/invalid calls and duplicates from creating issues.\n\nNodes\n\nanti double (Code) \u2013 builds a short-lived memory in workflow static data.\n\nIf ticket.id already seen (or fingerprint of projectKey+type+summary+description), mark as duplicate.\n\nIf action isn\u2019t create_ticket or required fields are missing, mark as invalid.\n\nIF for doubles\n\nTrue branch (duplicate/invalid) \u2192 blocked request (Code) returns { ok: true, duplicate: true | ignored: true } to the Webhook and stops.\n\nFalse branch (clean request) \u2192 continue.\n\nValidation\n\nSubmit the same form twice quickly \u2192 True path should fire once (no second Jira issue).\n\n\u201cTest connection\u201d from the app should be ignored and not create anything."
},
"typeVersion": 1
},
{
"id": "d933ae95-f6f6-4044-85de-f9a5fcca67e3",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1664,
-704
],
"parameters": {
"height": 1264,
"content": "## 5) Return result to the app\n\nPurpose: send a friendly response back to Streamlit.\n\nNodes\n\njira response (Set) \u2013 optional pass-through of the Jira response for visibility.\n\nResult (Code) \u2013 returns to the Webhook:\n{ ok: true, jiraKey: <KEY>, url: https://<domain>.atlassian.net/browse/<KEY> }\n\nUser experience\n\nStreamlit shows the created key and a link to open the ticket.\n\nMake sure to include your Jira space link in the Javascript code"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "10641e4a-3357-4aeb-939e-c22ae7a73db2",
"connections": {
"Result": {
"main": [
[]
]
},
"anti double": {
"main": [
[
{
"node": "raw data from streamlit",
"type": "main",
"index": 0
}
]
]
},
"jira response": {
"main": [
[
{
"node": "Result",
"type": "main",
"index": 0
}
]
]
},
"Processed data": {
"main": [
[
{
"node": "Jira HTTP request",
"type": "main",
"index": 0
}
]
]
},
"if for doubles": {
"main": [
[
{
"node": "Processed data",
"type": "main",
"index": 0
}
],
[
{
"node": "blocked request",
"type": "main",
"index": 0
}
]
]
},
"blocked request": {
"main": [
[]
]
},
"Jira HTTP request": {
"main": [
[
{
"node": "jira response",
"type": "main",
"index": 0
}
]
]
},
"Webhook streamlit": {
"main": [
[
{
"node": "anti double",
"type": "main",
"index": 0
}
]
]
},
"Process streamlit data": {
"main": [
[
{
"node": "if for doubles",
"type": "main",
"index": 0
}
]
]
},
"raw data from streamlit": {
"main": [
[
{
"node": "Process streamlit data",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automated workflow that creates Jira issues directly from Streamlit form submissions. Receives webhook data, validates and transforms it to Jira's API schema, creates the issue, and returns the ticket details to the frontend application.
Source: https://n8n.io/workflows/8156/ — 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.
This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di
This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .
This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c
Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.
📡 This workflow serves as the central Alpha Vantage API fetcher for Tesla trading indicators, delivering cleaned 20-point JSON outputs for three timeframes: , , and . It is required by the following a