{
  "id": "y0Yk7da21T4u9zlp",
  "meta": {
    "instanceId": "99f4e9e67f2a926c174453b6675a71cc5fb71c1fb19cfc06d50531053c661324",
    "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’s new. If nothing changed we simply log the check to GitLab for history. In short: scrape → compare → alert or store.\n\n## Setup steps\n\n1. Add your ScrapeGraphAI credential (menu → 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 “Certification URL Config” 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 – 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—ScrapeGraphAI’s schema awareness keeps the structure consistent even after you tweak it."
      },
      "typeVersion": 1
    },
    {
      "id": "08b02823-4383-499f-a708-d555de31d99f",
      "name": "Section – 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—each 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—like ignoring trivial whitespace edits—straightforward."
      },
      "typeVersion": 1
    },
    {
      "id": "9960ffa4-c9af-4a66-bd5e-12f2670d39f9",
      "name": "Section – 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’s 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—handy 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
      ],
      "webhookId": "1389760a-5c4b-4459-95e2-8b1c56f08a45",
      "parameters": {
        "otherOptions": {}
      },
      "typeVersion": 2.3
    }
  ],
  "active": false,
  "pinData": {},
  "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
          }
        ]
      ]
    }
  }
}