This workflow corresponds to n8n.io template #11634 — 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": "y0Yk7da21T4u9zlp",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Product Price Monitor with Mailgun and MongoDB",
"tags": [],
"nodes": [
{
"id": "f025c83b-a573-4b56-a0e8-3fbee6d1c304",
"name": "Webhook Listener",
"type": "n8n-nodes-base.webhook",
"position": [
720,
128
],
"parameters": {
"path": "public-transit-update",
"options": {},
"httpMethod": "POST",
"responseMode": "lastNode"
},
"typeVersion": 1
},
{
"id": "a46fe08f-20a4-42c1-aaf0-40714b7a8395",
"name": "Prepare Source URLs",
"type": "n8n-nodes-base.code",
"position": [
928,
128
],
"parameters": {
"jsCode": "// Build an array of transit authority URLs based on request body\nconst body = $json;\nconst routes = body.routes || ['RedLine', 'BlueLine'];\nconst baseUrls = {\n RedLine: 'https://www.mbta.com/schedules/Red',\n BlueLine: 'https://www.mbta.com/schedules/Blue'\n};\n\nreturn routes.map(route => ({\n json: {\n route,\n url: baseUrls[route] || ''\n }\n}));"
},
"typeVersion": 2
},
{
"id": "f1f8d3d0-5466-4dba-92df-e455f9ac7400",
"name": "Split URLs",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1184,
144
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "40056754-c138-41b6-a8dd-1a0fae49f90f",
"name": "Scrape Transit Data",
"type": "n8n-nodes-scrapegraphai.scrapegraphAi",
"position": [
1344,
128
],
"parameters": {
"userPrompt": "Extract the next five departures for the requested line along with any reported delay or service alerts. Return JSON with: line_name, next_departures:[{time, destination}], max_delay_minutes (number), alert_text (string).",
"websiteUrl": "={{ $json.url }}"
},
"typeVersion": 1
},
{
"id": "b87edbfa-b83a-45ef-8734-c169bfaab8ea",
"name": "Merge Results",
"type": "n8n-nodes-base.merge",
"position": [
1520,
128
],
"parameters": {
"mode": "passThrough",
"options": {}
},
"typeVersion": 2
},
{
"id": "b03df1ef-2b05-4658-8923-f0849a572c80",
"name": "Normalize & Deduplicate",
"type": "n8n-nodes-base.code",
"position": [
1728,
112
],
"parameters": {
"jsCode": "// Normalize field names and remove duplicate line entries\nconst items = $input.all();\nconst seen = new Set();\nconst output = [];\nitems.forEach(item => {\n const data = item.json;\n const key = data.line_name || data.route;\n if (!seen.has(key)) {\n seen.add(key);\n output.push({\n json: {\n ...data,\n checked_at: new Date().toISOString()\n }\n });\n }\n});\nreturn output;"
},
"typeVersion": 2
},
{
"id": "c8b52bd7-0213-4ae4-89f0-ba49170b2cc5",
"name": "Significant Delay?",
"type": "n8n-nodes-base.if",
"position": [
1920,
96
],
"parameters": {
"options": {},
"conditions": {
"conditions": [
{
"operator": {
"type": "number",
"operation": "larger"
},
"leftValue": "={{ $json.max_delay_minutes }}",
"rightValue": 10
}
]
}
},
"typeVersion": 2
},
{
"id": "8f007787-0902-4015-819a-bb6bd7947204",
"name": "Send Teams Alert",
"type": "n8n-nodes-base.microsoftTeams",
"position": [
2064,
80
],
"parameters": {
"chatId": {
"__rl": true,
"mode": "list",
"value": ""
},
"message": "={{ '<b>Transit delay alert:</b> ' + $json.line_name + ' experiencing ' + $json.max_delay_minutes + ' minute delay.<br/>' + ($json.alert_text || '') }}",
"options": {},
"resource": "chatMessage",
"contentType": "html"
},
"typeVersion": 2
},
{
"id": "2e2c46a2-503d-4f6d-9d9f-35ce8597a9d9",
"name": "Prepare File for Dropbox",
"type": "n8n-nodes-base.set",
"position": [
1760,
640
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "c542a632-fbbe-4300-9122-44ac71da9d6b",
"name": "Archive to Dropbox",
"type": "n8n-nodes-base.dropbox",
"position": [
1952,
640
],
"parameters": {
"path": "={{ '/transit-updates/' + $json.fileName }}",
"fileContent": "={{ $json.fileContent }}"
},
"typeVersion": 1
},
{
"id": "a5dcb0f0-5661-45c4-9902-155e4c14cdc8",
"name": "Workflow Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
64,
-224
],
"parameters": {
"width": 550,
"height": 834,
"content": "## How it works\n\nThis webhook-driven workflow listens for a POST request that contains the routes a traveler cares about. As soon as the call hits the Webhook Trigger, a Code node builds a list of official transit-authority URLs for each requested line and hands them to a Split in Batches node so every page can be scraped in parallel. ScrapeGraphAI reads each page, understands the timetable layout and the live status widget, and returns clean JSON with the next departures as well as the biggest delay currently reported. A Merge node reunites the parallel branches, after which a small Code step normalizes field names, removes duplicates and calculates the longest delay per line. An IF node checks whether any delay is above ten minutes. If so, a Microsoft Teams alert lands in your operations channel. Regardless of delay severity, every run produces a nicely formatted JSON file that is archived to Dropbox for later analysis.\n\n## Setup steps\n\n1. Add your ScrapeGraphAI API credential\n2. Add Dropbox credential with write access\n3. Add Microsoft Teams OAuth2 credential and fill team/channel IDs\n4. Adjust the URL list in the \u201cPrepare Source URLs\u201d node\n5. Change delay threshold in the IF node if needed\n6. Enable the workflow and copy the webhook URL to your mobile app\n"
},
"typeVersion": 1
},
{
"id": "9643fb7a-0f0f-455e-ba56-b3f119c2c54c",
"name": "Section \u2013 Trigger & URLs",
"type": "n8n-nodes-base.stickyNote",
"position": [
672,
-240
],
"parameters": {
"color": 7,
"width": 450,
"height": 782,
"content": "## Trigger & URL Builder\n\nThis group kick-starts the automation each time your mobile app or another service calls the webhook. The **Webhook Listener** instantly accepts the POST request and returns an acknowledgement so your client doesn\u2019t wait around. The raw body is passed into **Prepare Source URLs**, a JavaScript Code node that translates the requested route names into the official timetable URLs published by the transit authorities. By centralising URL construction here you can easily support new cities without touching the scraping logic. The output is an array of small JSON items, each carrying a single `url` field and the matching `route` identifier. **Split URLs** then feeds those items downstream one-by-one so ScrapeGraphAI can run multiple extractions in parallel without exceeding rate limits."
},
"typeVersion": 1
},
{
"id": "0c365170-7c26-4ac0-a7cc-42abb2ea1cae",
"name": "Section \u2013 Scraping Layer",
"type": "n8n-nodes-base.stickyNote",
"position": [
1168,
-240
],
"parameters": {
"color": 7,
"width": 450,
"height": 782,
"content": "## Scraping Layer\n\nThe scraping layer is where the heavy lifting occurs. **Scrape Transit Data** leverages ScrapeGraphAI\u2019s LLM-powered parser to read the live timetable or alert page and transform it into structured JSON\u2014no brittle CSS selectors required. Because the Split node releases one URL at a time, multiple ScrapeGraphAI executions can run side-by-side, keeping the overall response time low. **Merge Results** operates in pass-through mode so everything the scraper returns is funnelled back into a single stream. This design means you can add more URLs\u2014perhaps for buses or trams\u2014without changing any logic further down the pipeline: just add the URLs and let the scraper plus merge combination handle aggregation automatically."
},
"typeVersion": 1
},
{
"id": "d0794c25-d4d7-4043-bcae-0bdb9891ab20",
"name": "Section \u2013 Processing & Alerts",
"type": "n8n-nodes-base.stickyNote",
"position": [
1728,
-288
],
"parameters": {
"color": 7,
"width": 450,
"height": 542,
"content": "## Processing & Alerting\n\nOnce all raw pages are scraped, **Normalize & Deduplicate** tidies the data. It standardises field names, stamps each record with the current timestamp and removes any duplicate line entries that could appear if an authority mirrors the same information on multiple pages. The cleaned dataset flows into **Significant Delay?**, an IF node that compares the calculated `max_delay_minutes` against a configurable threshold (default is 10). Items on the TRUE branch head straight to **Send Teams Alert**, which posts an HTML-formatted card into your chosen Microsoft Teams channel so riders or operations staff see issues instantly. Items that don\u2019t meet the alert criteria bypass the Teams node but still continue to archiving, ensuring no data is lost."
},
"typeVersion": 1
},
{
"id": "645f9075-f12e-43bd-8063-4cf59df9936d",
"name": "Section \u2013 Storage & Archiving",
"type": "n8n-nodes-base.stickyNote",
"position": [
1728,
256
],
"parameters": {
"color": 7,
"width": 450,
"height": 622,
"content": "## Storage & Archiving\n\nEvery execution\u2014alert-worthy or not\u2014gets recorded for historical analysis. **Prepare File for Dropbox** assembles a concise JSON representation of each item and crafts a timestamped file name so nothing is overwritten. The node\u2019s output carries two simple properties: `fileName` and `fileContent`. **Archive to Dropbox** then uploads the file into a `/transit-updates` folder inside your connected Dropbox account, creating the directory if it does not already exist. Storing plain JSON files keeps costs low and makes it trivial to import the records into BI tools later. Because the Teams branch also flows into the Set node, you end up with a single, unified audit trail regardless of whether a delay alert was triggered."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "2e5187cc-e878-4613-9573-019670cd1f2b",
"connections": {
"Split URLs": {
"main": [
[
{
"node": "Scrape Transit Data",
"type": "main",
"index": 0
}
]
]
},
"Merge Results": {
"main": [
[
{
"node": "Normalize & Deduplicate",
"type": "main",
"index": 0
}
]
]
},
"Send Teams Alert": {
"main": [
[
{
"node": "Prepare File for Dropbox",
"type": "main",
"index": 0
}
]
]
},
"Webhook Listener": {
"main": [
[
{
"node": "Prepare Source URLs",
"type": "main",
"index": 0
}
]
]
},
"Significant Delay?": {
"main": [
[
{
"node": "Send Teams Alert",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare File for Dropbox",
"type": "main",
"index": 0
}
]
]
},
"Prepare Source URLs": {
"main": [
[
{
"node": "Split URLs",
"type": "main",
"index": 0
}
]
]
},
"Scrape Transit Data": {
"main": [
[
{
"node": "Merge Results",
"type": "main",
"index": 0
}
]
]
},
"Normalize & Deduplicate": {
"main": [
[
{
"node": "Significant Delay?",
"type": "main",
"index": 0
}
]
]
},
"Prepare File for Dropbox": {
"main": [
[
{
"node": "Archive to Dropbox",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
⚠️ COMMUNITY TEMPLATE DISCLAIMER: This is a community-contributed template that uses ScrapeGraphAI (a community node). Please ensure you have the ScrapeGraphAI community node installed in your n8n instance before using this template.
Source: https://n8n.io/workflows/11634/ — 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.
⚠️ COMMUNITY TEMPLATE DISCLAIMER: This is a community-contributed template that uses ScrapeGraphAI (a community node). Please ensure you have the ScrapeGraphAI community node installed in your n8n ins
⚠️ COMMUNITY TEMPLATE DISCLAIMER: This is a community-contributed template that uses ScrapeGraphAI (a community node). Please ensure you have the ScrapeGraphAI community node installed in your n8n ins
zaad9_teams_dane. Uses emailSend, airtable, slack, microsoftTeams. Webhook trigger; 7 nodes.
BP_check. Uses googleSheets, @n-octo-n/n8n-nodes-json-database, httpRequest, itemLists. Webhook trigger; 99 nodes.
v25.1.3. Uses httpRequest, mySql, n8n-nodes-zohozeptomail. Webhook trigger; 98 nodes.