{
  "id": "y0Yk7da21T4u9zlp",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Certification Requirement Tracker with Rocket.Chat and GitLab",
  "tags": [],
  "nodes": [
    {
      "id": "5ef44be2-5506-4abd-9177-07c70b5b5522",
      "name": "Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -816,
        96
      ],
      "parameters": {
        "width": 550,
        "height": 834,
        "content": "## How it works\n\nThis workflow keeps an eye on changing certification requirements so busy professionals never miss an update. A daily Schedule Trigger kicks things off and feeds a small Code node that lists the websites of certification bodies. The Split-In-Batches node makes sure we process one certification at a time, then ScrapeGraphAI pulls the current requirement text, last-updated date, and renewal interval straight from each page. An IF node watches for scraping errors; if something goes wrong a Rocket.Chat alert fires immediately so you know to investigate. Otherwise we grab the previously saved JSON file from a GitLab repository, merge old and new data, and run a comparison in a Code node. If anything has changed we create/overwrite the file in GitLab *and* drop a Rocket.Chat message that explains what\u2019s new. If nothing changed we simply log the check to GitLab for history. In short: scrape \u2192 compare \u2192 alert or store.\n\n## Setup steps\n\n1. Add your ScrapeGraphAI credential (menu \u2192 credentials).\n2. Add a GitLab credential that points to a repo you own.\n3. Add a Rocket.Chat credential with a bot/user that can post to your alerts channel.\n4. Open the \u201cCertification URL Config\u201d Code node and list the URLs and IDs you want tracked.\n5. Adjust the schedule if you prefer weekly or monthly runs.\n6. Enable the workflow and watch GitLab & Rocket.Chat for the next check."
      },
      "typeVersion": 1
    },
    {
      "id": "cccca1d1-f591-41c6-9253-35fa82916685",
      "name": "Section \u2013 Data Collection",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 450,
        "height": 862,
        "content": "## Data Collection & Scraping\n\nThis cluster is responsible for fetching the latest certification information every day. The Schedule Trigger runs at the cadence you choose (default: once every 24 hours). Immediately after, the **Certification URL Config** Code node returns an array of objects, each carrying a unique `certId` and the website URL of an industry body. **Split In Batches** ensures that each certification is handled individually, preventing API-rate explosions and simplifying downstream logic. Finally, **ScrapeGraphAI** reads the live page and uses the provided natural-language prompt to extract fields such as `certName`, `requirementText`, `lastUpdated`, and `renewalIntervalYears`. All scraped data is output as a tidy JSON object that downstream nodes can process without extra parsing. Feel free to extend the prompt with additional fields\u2014ScrapeGraphAI\u2019s schema awareness keeps the structure consistent even after you tweak it."
      },
      "typeVersion": 1
    },
    {
      "id": "08b02823-4383-499f-a708-d555de31d99f",
      "name": "Section \u2013 Change Detection",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 450,
        "height": 526,
        "content": "## Comparison Logic\n\nAfter a clean scrape, the workflow needs to know whether anything actually changed. First we fetch the previously stored JSON file from GitLab\u2014each certification lives at `/certifications/{certId}.json` on the `main` branch. A **Merge (By Position)** node combines the freshly scraped item (input 1) with the previously stored item (input 2). The downstream **Detect Changes** Code node now has both datasets side-by-side and can efficiently compare the requirement text, renewal interval, or any other field you add later. The node sets a Boolean `changed` flag plus a human-readable diff string. By modularising comparison here we isolate all change-detection business logic in one place, making future tweaks\u2014like ignoring trivial whitespace edits\u2014straightforward."
      },
      "typeVersion": 1
    },
    {
      "id": "9960ffa4-c9af-4a66-bd5e-12f2670d39f9",
      "name": "Section \u2013 Actions",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1264,
        176
      ],
      "parameters": {
        "color": 7,
        "width": 946,
        "height": 798,
        "content": "## Conditional Actions & Storage\n\nOnce the workflow knows whether a requirement changed, it splits into two clear outcomes. When `changed` is **true**, the true branch updates or creates the JSON file in GitLab, writes a concise commit message, and then posts a Rocket.Chat alert summarising what\u2019s new. This immediately notifies credential-holding professionals or compliance officers. When `changed` is **false**, the false branch quietly creates a log issue in GitLab noting that the daily check ran with no changes\u2014handy for audit trails without flooding chat channels. Because these actions are downstream of the change-detection IF node, the workflow avoids redundant commits and alerts, focusing attention only when real updates occur."
      },
      "typeVersion": 1
    },
    {
      "id": "80f9fd18-49bc-43c3-afda-ecbb2686dfd1",
      "name": "Daily Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -176,
        480
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "91886f51-151d-4bed-9ea0-3aad3026b8f9",
      "name": "Certification URL Config",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        480
      ],
      "parameters": {
        "jsCode": "// List certifications you want to track\nreturn [\n  {\n    json: {\n      certId: 'pmp',\n      url: 'https://www.pmi.org/certifications/pmp'\n    }\n  },\n  {\n    json: {\n      certId: 'cissp',\n      url: 'https://www.isc2.org/Certifications/CISSP'\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "da18a302-79b3-4488-9562-160d880db504",
      "name": "Split In Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        224,
        480
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "dcb6f065-8799-4b5f-8329-525270aa6f5e",
      "name": "Scrape Requirement Data",
      "type": "n8n-nodes-scrapegraphai.scrapegraphAi",
      "position": [
        432,
        480
      ],
      "parameters": {
        "userPrompt": "Extract the certification name, full requirement description, last updated date, and renewal interval in years. Return JSON with keys: certName, requirementText, lastUpdated, renewalIntervalYears.",
        "websiteUrl": "={{ $json.url }}"
      },
      "typeVersion": 1
    },
    {
      "id": "f511b6a6-da7d-41cd-b052-4b0fb758deaa",
      "name": "Scrape Error?",
      "type": "n8n-nodes-base.if",
      "position": [
        624,
        480
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.error }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "235944b4-69d6-4ba6-826e-b63f82d7bf46",
      "name": "Fetch Previous Data",
      "type": "n8n-nodes-base.gitlab",
      "position": [
        624,
        688
      ],
      "parameters": {
        "filePath": "={{ '/certifications/' + $json.certId + '.json' }}",
        "resource": "file",
        "operation": "get",
        "additionalParameters": {}
      },
      "typeVersion": 1
    },
    {
      "id": "350245b4-c597-491a-b5ac-bcdeee2027e4",
      "name": "Merge Current & Previous",
      "type": "n8n-nodes-base.merge",
      "position": [
        832,
        688
      ],
      "parameters": {
        "mode": "mergeByPosition",
        "options": {}
      },
      "typeVersion": 2
    },
    {
      "id": "cff97b55-1ca4-4470-b157-7ebb58766eab",
      "name": "Detect Changes",
      "type": "n8n-nodes-base.code",
      "position": [
        1024,
        688
      ],
      "parameters": {
        "jsCode": "const [current, previous] = $input.all();\n\nconst scraped = current.json;\nconst prevContent = previous.json;\nlet prevParsed;\ntry {\n  prevParsed = prevContent ? JSON.parse(prevContent) : null;\n} catch (_) {\n  prevParsed = null;\n}\n\nconst changed = !prevParsed || JSON.stringify(scraped) !== JSON.stringify(prevParsed);\n\nreturn [{\n  json: {\n    ...scraped,\n    certId: current.json.certId,\n    changed,\n    prevData: prevParsed || null\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "594513c6-e104-43e8-801a-472730dee3f2",
      "name": "Requirement Changed?",
      "type": "n8n-nodes-base.if",
      "position": [
        1232,
        688
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.changed }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "3d437f5f-4930-4096-ac97-cb4985c57874",
      "name": "Prepare GitLab File",
      "type": "n8n-nodes-base.set",
      "position": [
        1424,
        592
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "d8144ac1-c244-4e7c-8a8e-7ebddc12b9dc",
      "name": "Save Updated Requirement",
      "type": "n8n-nodes-base.gitlab",
      "position": [
        1632,
        592
      ],
      "parameters": {
        "branch": "main",
        "filePath": "={{ $json.filePath }}",
        "resource": "file",
        "operation": "edit",
        "commitMessage": "={{ $json.commitMsg }}"
      },
      "typeVersion": 1
    },
    {
      "id": "fcd9a3b8-4653-4cc1-a2e9-74e3f63b5f45",
      "name": "Craft Alert Message",
      "type": "n8n-nodes-base.set",
      "position": [
        1824,
        592
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "84651cb9-5ed9-4284-a2b3-8aa572327ba6",
      "name": "Log No-Change Issue",
      "type": "n8n-nodes-base.gitlab",
      "position": [
        1424,
        784
      ],
      "parameters": {
        "title": "No change for {{$json.certId}} on {{$now.toFormat('yyyy-LL-dd')}}",
        "labels": [],
        "assignee_ids": []
      },
      "typeVersion": 1
    },
    {
      "id": "6ec29c92-af53-416d-87c2-e032efaed2b2",
      "name": "Send a message",
      "type": "n8n-nodes-base.slack",
      "position": [
        2032,
        592
      ],
      "parameters": {
        "otherOptions": {}
      },
      "typeVersion": 2.3
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d5159dac-681f-4344-a7d1-490845407953",
  "connections": {
    "Daily Trigger": {
      "main": [
        [
          {
            "node": "Certification URL Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Error?": {
      "main": [
        [],
        [
          {
            "node": "Fetch Previous Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Current & Previous",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect Changes": {
      "main": [
        [
          {
            "node": "Requirement Changed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split In Batches": {
      "main": [
        [
          {
            "node": "Scrape Requirement Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Craft Alert Message": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Previous Data": {
      "main": [
        [
          {
            "node": "Merge Current & Previous",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Prepare GitLab File": {
      "main": [
        [
          {
            "node": "Save Updated Requirement",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Requirement Changed?": {
      "main": [
        [
          {
            "node": "Prepare GitLab File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log No-Change Issue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Requirement Data": {
      "main": [
        [
          {
            "node": "Scrape Error?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Certification URL Config": {
      "main": [
        [
          {
            "node": "Split In Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Current & Previous": {
      "main": [
        [
          {
            "node": "Detect Changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Updated Requirement": {
      "main": [
        [
          {
            "node": "Craft Alert Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}