{
  "id": "rOltbkkDJQrk5R2f",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Snyk Vulnerability Automation Workflow with Webhook, Jira, Slack & Airtable",
  "tags": [],
  "nodes": [
    {
      "id": "15e19984-9133-4985-ab4a-bb61c19e1cdf",
      "name": "Receive Vulnerability Data",
      "type": "n8n-nodes-base.webhook",
      "position": [
        96,
        -976
      ],
      "parameters": {
        "path": "snyk-vuln",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 1
    },
    {
      "id": "2fbdc06e-b08d-4abd-b1d0-ef4af33a94f5",
      "name": "Normalize Vulnerability Data",
      "type": "n8n-nodes-base.function",
      "position": [
        320,
        -976
      ],
      "parameters": {
        "functionCode": "// Access the body of the webhook\nconst body = $json.body || {};\nlet vulns = [];\n\n// Check for different possible shapes\nif (Array.isArray(body.vulnerabilities)) vulns = body.vulnerabilities;\nelse if (Array.isArray(body.issues)) vulns = body.issues;\nelse if (Array.isArray(body.vulns)) vulns = body.vulns;\nelse if (Array.isArray(body)) vulns = body;\nelse if (body.issue) vulns = [body.issue];\n\n// Return normalized array\nreturn (vulns.length ? vulns : [{}]).map(v => ({ json: v }));\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d0e9d57e-0c84-4dc6-a738-12821bc57367",
      "name": "Validate Vulnerability Fields",
      "type": "n8n-nodes-base.code",
      "position": [
        544,
        -976
      ],
      "parameters": {
        "jsCode": "const results = [];\n\nfor (const item of $input.all()) {\n  const raw = item.json;\n\n  const id = raw.id || raw.issueId || raw.name || '';\n  const title = raw.title || raw.description || id;\n\n  const cvssRaw = raw.cvssScore || raw.cvss || raw.cvss_score;\n  const cvss = parseFloat(cvssRaw);\n\n  const packageName = raw.package || raw.packageName || (raw.pkg && raw.pkg.name) || '';\n  const vulnUrl = raw.url || raw.reference || raw.issueUrl || '';\n\n  // Force strict boolean\n  const isValid =\n    id !== '' &&\n    title !== '' &&\n    !isNaN(cvss) &&\n    packageName !== '' &&\n    vulnUrl !== '';\n\n  results.push({\n    json: { id, title, cvss, packageName, vulnUrl, raw, isValid }\n  });\n}\n\nreturn results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d9b167fb-b26f-4546-abad-84d3235dd015",
      "name": "Check if Vulnerability is Valid",
      "type": "n8n-nodes-base.if",
      "position": [
        768,
        -976
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.isValid }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "33e3257e-a5c5-4397-bb32-c9b9bf679f17",
      "name": "Notify Malformed Payload",
      "type": "n8n-nodes-base.slack",
      "position": [
        1088,
        -832
      ],
      "parameters": {
        "text": "=*Malformed Payload Detected*\nSome required fields are missing. Manual review Required.\n```{{$json.raw ? JSON.stringify($json.raw,null,2) : 'N/A'}}```",
        "select": "channel",
        "channelId": {
          "mode": "list",
          "value": "C09S57E2JQ2"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "31c3a368-77fa-44ed-9c21-c742718c5d7d",
      "name": "Classify Vulnerability Severity",
      "type": "n8n-nodes-base.function",
      "position": [
        1056,
        -1136
      ],
      "parameters": {
        "functionCode": "const cvss = Number($json.cvss);\nlet severityLabel, severityText;\n\nif (cvss >= 9.0) { severityLabel = 'CVSS-HIGHEST'; severityText = 'Highest'; }\nelse if (cvss >= 7.0) { severityLabel = 'CVSS-HIGH'; severityText = 'High'; }\nelse if (cvss >= 4.0) { severityLabel = 'CVSS-MEDIUM'; severityText = 'Medium'; }\nelse { severityLabel = 'CVSS-LOW'; severityText = 'Low'; }\n\nreturn [{ json: { ...$json, severityLabel, severityText } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "2b965cb3-fa61-48d6-b0bb-8cef1c377398",
      "name": "Generate Vulnerability Key",
      "type": "n8n-nodes-base.function",
      "position": [
        1280,
        -1136
      ],
      "parameters": {
        "functionCode": "const id = $json.id || ($json.raw && $json.raw.id) || '';\nconst vulnKey = id ? `vuln-${id}` : '';\nreturn [{ json: { ...$json, vulnKey } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "8693b884-8da8-4ddd-8a4f-2433bb0749cd",
      "name": "Check Existing Jira Issue",
      "type": "n8n-nodes-base.jira",
      "position": [
        1504,
        -1136
      ],
      "parameters": {
        "options": {
          "jql": "=project = KAN AND labels IN (\"{{ $json.vulnKey }}\") AND statusCategory != Done\n",
          "fields": "*all"
        },
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "jiraSoftwareCloudApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "5dc08eb5-ce6b-46be-8b5b-dec1d8998893",
      "name": "Determine if Duplicate",
      "type": "n8n-nodes-base.code",
      "position": [
        1712,
        -1136
      ],
      "parameters": {
        "jsCode": "// Original vulnerability data from previous node\nconst original = $items(\"Generate Vulnerability Key\", 0, 0).json;\n\n// Jira search output \u2192 array of issue objects\nconst jiraArray = Array.isArray($input.all()) ? $input.all() : [];\n\n// Filter out empty objects like [{}]\nconst issues = jiraArray.filter(item => Object.keys(item.json).length > 0);\n\n// Extract issue JSON (n8n wraps each item as {json: {...}})\nconst issue = issues.length > 0 ? issues[0].json : null;\n\nreturn [{\n  json: {\n    ...original,\n    isDuplicate: issue !== null,\n    jira: issue\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "bb8d334c-c5db-4d34-ae56-5c8de33cd371",
      "name": "Duplicate Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        1920,
        -1136
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.isDuplicate}}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "66a6b37b-41e8-46a0-b178-07d13c598919",
      "name": "Update Existing Jira Issue",
      "type": "n8n-nodes-base.jira",
      "position": [
        2160,
        -1152
      ],
      "parameters": {
        "issueKey": "={{ $json.jira.key }}",
        "operation": "update",
        "updateFields": {
          "labels": "={{ $json.jira.fields.labels }}",
          "summary": "={{ $json.jira.fields.summary }}"
        }
      },
      "credentials": {
        "jiraSoftwareCloudApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "49619bcc-4183-46cb-882b-432049e0ec27",
      "name": "Notify Duplicate Update",
      "type": "n8n-nodes-base.slack",
      "position": [
        2160,
        -1344
      ],
      "parameters": {
        "text": "=*Vulnerability Already Exists (Updated)*\n*Jira:* https://yourcompany.atlassian.net/browse/{{ $json.jira.key }}}}",
        "select": "channel",
        "channelId": {
          "mode": "list",
          "value": "C09S57E2JQ2"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "bf4d240e-c295-4221-b738-bd1f4b3e207d",
      "name": "Create New Jira Vulnerability",
      "type": "n8n-nodes-base.jira",
      "position": [
        2160,
        -768
      ],
      "parameters": {
        "project": {
          "mode": "list",
          "value": "10000"
        },
        "summary": "=[VULN] {{ $('Generate Vulnerability Key').item.json.title }} (CVSS: {{ $('Generate Vulnerability Key').item.json.cvss }})",
        "issueType": {
          "mode": "list",
          "value": "10003"
        },
        "additionalFields": {
          "labels": "={{ [$('Generate Vulnerability Key').item.json.vulnKey] }}",
          "description": "=Vulnerability Key: {{ $('Generate Vulnerability Key').item.json.vulnKey }}\nVulnerability Issue: {{ $('Generate Vulnerability Key').item.json.title }}\nSeverity: {{ $('Generate Vulnerability Key').item.json.severityText }}\nCVSS:{{ $('Generate Vulnerability Key').item.json.cvss }}\nURL: {{ $('Generate Vulnerability Key').item.json.raw.url }}",
          "customFieldsUi": {
            "customFieldsValues": [
              {
                "fieldId": {
                  "__rl": true,
                  "mode": "list",
                  "value": "customfield_10035",
                  "cachedResultName": "Vulnerability"
                },
                "fieldValue": "={{ $('Generate Vulnerability Key').item.json.id }}"
              }
            ]
          }
        }
      },
      "credentials": {
        "jiraSoftwareCloudApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8a567df7-14c1-442b-bdf7-658df273736b",
      "name": "Notify New Vulnerability",
      "type": "n8n-nodes-base.slack",
      "position": [
        2400,
        -768
      ],
      "parameters": {
        "text": "=*New Vulnerability Created*\n*Title:* {{ $('Classify Vulnerability Severity').item.json.title }}\n*CVSS:* {{ $('Classify Vulnerability Severity').item.json.cvss }}\n*Severity:* {{ $('Classify Vulnerability Severity').item.json.severityText }}\n*Package:* {{ $('Classify Vulnerability Severity').item.json.packageName }}\n*Jira:* https://mycompany.atlassian.net/browse/{{$node[\"Create New Jira Vulnerability\"].json.key}}\n*Details:* {{ $('Classify Vulnerability Severity').item.json.vulnUrl }}",
        "select": "channel",
        "channelId": {
          "mode": "list",
          "value": "C09S57E2JQ2"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "0a675201-2cb9-47fe-8510-03c2132d7699",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -16,
        -1056
      ],
      "parameters": {
        "color": 7,
        "width": 272,
        "height": 240,
        "content": "Entry point webhook that receives vulnerability payload from Snyk or other source"
      },
      "typeVersion": 1
    },
    {
      "id": "2f490329-2fd2-4287-a378-d7c6f081ba28",
      "name": "Create a record",
      "type": "n8n-nodes-base.airtable",
      "position": [
        2656,
        -768
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appF2iYPgVqqyXDC1",
          "cachedResultUrl": "https://airtable.com/appF2iYPgVqqyXDC1",
          "cachedResultName": "n8n Demo"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblzOTBGc2PYkSM28",
          "cachedResultUrl": "https://airtable.com/appF2iYPgVqqyXDC1/tblzOTBGc2PYkSM28",
          "cachedResultName": "Vulnerability"
        },
        "columns": {
          "value": {
            "CVSS": "={{ $('Generate Vulnerability Key').item.json.cvss }}",
            "Title": "={{ $('Generate Vulnerability Key').item.json.title }}",
            "VulnURL": "={{ $('Generate Vulnerability Key').item.json.vulnUrl }}",
            "Severity": "={{ $('Generate Vulnerability Key').item.json.severityText }}",
            "PackageName": "={{ $('Generate Vulnerability Key').item.json.packageName }}",
            "Vulnerability key": "={{ $('Generate Vulnerability Key').item.json.vulnKey }}"
          },
          "schema": [
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "CVSS",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "CVSS",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PackageName",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "PackageName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "VulnURL",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "VulnURL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Vulnerability key",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Vulnerability key",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Severity",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Severity",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Attachment Summary",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Attachment Summary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "ae6751c6-b681-4310-b01a-68c3e53eb65f",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        -1152
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 336,
        "content": "## Normalize & Validate Vulnerability Data\nEnsures all incoming vulnerability data is converted into a consistent format and checks that required fields like ID, title, CVSS, package, and URL are present and valid."
      },
      "typeVersion": 1
    },
    {
      "id": "516b2134-4f02-490f-80cc-1d41838a61b7",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        -1088
      ],
      "parameters": {
        "color": 7,
        "width": 246,
        "height": 272,
        "content": "Valid vulnerabilities continue automatically; invalid ones require manual review."
      },
      "typeVersion": 1
    },
    {
      "id": "d8c29333-6d89-4b58-8f3f-82b291c27f3c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        -896
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 256,
        "content": "Sends Slack alert if payload is missing required fields"
      },
      "typeVersion": 1
    },
    {
      "id": "a767d3ee-b995-488c-b0bb-5a2c925c8146",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        -1344
      ],
      "parameters": {
        "color": 7,
        "width": 1040,
        "height": 416,
        "content": "## Vulnerability Deduplication & Tracking\n\nThis process assigns severity labels to vulnerabilities, creates a unique key for each, checks Jira for duplicates, marks duplicates if found, and either updates existing issues or creates new ones."
      },
      "typeVersion": 1
    },
    {
      "id": "ad6781f4-7684-469a-a533-2e95d2c83915",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2096,
        -1536
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 560,
        "content": "## Handling New Vulnerabilities\nWhen a vulnerability already exists in Jira, the system updates the existing Jira ticket with the latest details and sends a Slack message to let the team know about the update."
      },
      "typeVersion": 1
    },
    {
      "id": "44e88dfa-f999-4401-8816-7536ba142bb0",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2112,
        -896
      ],
      "parameters": {
        "color": 7,
        "width": 896,
        "height": 320,
        "content": "## Handling New Vulnerabilities\nWhen a vulnerability is new, the system creates a fresh Jira ticket for it, sends a Slack message to notify the team, and saves the details in Airtable for tracking."
      },
      "typeVersion": 1
    },
    {
      "id": "53255fed-48b3-4c5c-9939-e4afb737e742",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -960,
        -1776
      ],
      "parameters": {
        "width": 608,
        "height": 704,
        "content": "## How It Works\nThis workflow takes security issues coming from Snyk and automatically handles them in Jira. First, it cleans and checks the incoming data to make sure all required details are present. Then it calculates the severity level and creates a unique ID for each vulnerability.\nNext, it looks in Jira to see if the same issue already exists.\n\nIf it\u2019s new, the workflow creates a fresh Jira ticket, sends a Slack alert, and stores the data in Airtable.\nIf it already exists, the workflow updates the existing Jira ticket and sends a Slack notification to let the team know.\nIf the incoming data is incomplete, it sends a Slack message for manual review.\nOverall, it fully automates the tracking of vulnerabilities with minimal human effort.\n\n## Setup Steps\n**1.** Connect Snyk webhook to the \u201cReceive Vulnerability\u201d node.\n\n**2.** Verify Normalize + Validation nodes are mapping fields correctly.\n\n**3.** Update severity logic or field names if needed.\n\n**4.** In Jira nodes, set the correct project, issue type, and custom fields.\n\n**5.** Confirm Airtable API key/table mapping (if used).\n\n**6.** Add Slack credentials and choose the alert channel.\n\n**7.** Test the workflow using sample payloads and verify both \u201cnew\u201d and \u201cduplicate\u201d paths.\n\n**8.** When if data is new add on Airtable database\n\n**9.** Activate the workflow once both branches work correctly."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "77f768ec-2c81-4b3b-8e98-2d2b2a05b3f0",
  "connections": {
    "Duplicate Found?": {
      "main": [
        [
          {
            "node": "Update Existing Jira Issue",
            "type": "main",
            "index": 0
          },
          {
            "node": "Notify Duplicate Update",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create New Jira Vulnerability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Determine if Duplicate": {
      "main": [
        [
          {
            "node": "Duplicate Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify New Vulnerability": {
      "main": [
        [
          {
            "node": "Create a record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Existing Jira Issue": {
      "main": [
        [
          {
            "node": "Determine if Duplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Vulnerability Key": {
      "main": [
        [
          {
            "node": "Check Existing Jira Issue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive Vulnerability Data": {
      "main": [
        [
          {
            "node": "Normalize Vulnerability Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Vulnerability Data": {
      "main": [
        [
          {
            "node": "Validate Vulnerability Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create New Jira Vulnerability": {
      "main": [
        [
          {
            "node": "Notify New Vulnerability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Vulnerability Fields": {
      "main": [
        [
          {
            "node": "Check if Vulnerability is Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Vulnerability is Valid": {
      "main": [
        [
          {
            "node": "Classify Vulnerability Severity",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify Malformed Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Vulnerability Severity": {
      "main": [
        [
          {
            "node": "Generate Vulnerability Key",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}