This workflow corresponds to n8n.io template #11063 — we link there as the canonical source.
This workflow follows the Agent → Google Sheets recipe pattern — see all workflows that pair these two integrations.
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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "c65eb0e0-66c6-4145-982b-80a71f393411",
"name": "Start Workflow (Manual Run)",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-800,
-96
],
"parameters": {},
"typeVersion": 1
},
{
"id": "e61dbb92-37f8-4159-be66-ec07314e4ffe",
"name": "Config: Alert Parameters",
"type": "n8n-nodes-base.set",
"position": [
-432,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "ebdc42b1-9d74-4af5-a24b-b506430884ef",
"name": "alert_threshold_percent",
"type": "number",
"value": 10
}
]
}
},
"typeVersion": 3.4
},
{
"id": "fd66e345-e89a-466b-bfb4-d3166c4b174f",
"name": "Decodo: Fetch Full HTML",
"type": "@decodo/n8n-nodes-decodo.decodo",
"position": [
736,
16
],
"parameters": {
"geo": "United States",
"url": "={{ $json.URL }}"
},
"credentials": {
"decodoApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "54e2a208-9cf9-4d09-b8e6-be9a43bfe0b5",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
1328,
256
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "e410bbcf-7a3e-471d-bb24-64d0cf9dc6cd",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1520,
256
],
"parameters": {
"jsonSchemaExample": "[\n {\n \"plan_name\": \"string (e.g., Basic, Pro, Enterprise)\",\n \"price_usd\": \"string (monetary value, e.g., $99.00)\",\n \"billing_cycle\": \"string (e.g., monthly, annual, one-time)\",\n \"key_feature_summary\": \"string (A concise, 10-word summary of the plan's main value)\"\n }\n]"
},
"typeVersion": 1.3
},
{
"id": "46470c28-7e65-46c6-bea8-1583d491617d",
"name": "Discard Error Item",
"type": "n8n-nodes-base.noOp",
"position": [
2224,
256
],
"parameters": {},
"typeVersion": 1
},
{
"id": "19016499-b7cf-4479-9aaa-c4bef22e49f3",
"name": "Update row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
3744,
16
],
"parameters": {
"columns": {
"value": {
"Old Plans": "={{ $('Loop Monitor Each Plan').item.json['Last Plans'] }}",
"Last Plans": "={{ $('AI Agent: Extract Plans').item.json.output }}",
"Updated At": "={{ $now }}",
"row_number": "={{ $('Loop Monitor Each Plan').item.json.row_number }}"
},
"schema": [
{
"id": "Name",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "URL",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Old Plans",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Old Plans",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Last Plans",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Last Plans",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Updated At",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Updated At",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"row_number"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/150NeYyp726AQEs-Rt61hLSDKOAXU0kKDiwF_0De7Fww/edit#gid=0",
"cachedResultName": "competitors"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "150NeYyp726AQEs-Rt61hLSDKOAXU0kKDiwF_0De7Fww",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/150NeYyp726AQEs-Rt61hLSDKOAXU0kKDiwF_0De7Fww/edit?usp=drivesdk",
"cachedResultName": "Competitors"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"executeOnce": true,
"typeVersion": 4.7
},
{
"id": "0cad87b4-7f0b-42f9-9e44-ab24d964f53c",
"name": "If: Alerts to Send?",
"type": "n8n-nodes-base.if",
"position": [
2816,
-160
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "504be28e-f5df-4df8-97b9-cd9bc8db3f0f",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.alert_items.length }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "e87a7f1d-335e-4b2d-be5f-25767afe7d15",
"name": "Split Alerts for Notification",
"type": "n8n-nodes-base.splitOut",
"position": [
3216,
-288
],
"parameters": {
"options": {},
"fieldToSplitOut": "alert_items"
},
"typeVersion": 1
},
{
"id": "307fc036-767d-4700-a9cf-3ce872507f79",
"name": "Send Competitor Price Alert",
"type": "n8n-nodes-base.slack",
"position": [
3424,
-288
],
"parameters": {
"text": "=*\ud83d\uded1 COMPETITOR PRICE SHIFT DETECTED! \ud83d\uded1*\n\nCompany Name: *{{ $('Loop Monitor Each Plan').item.json.Name }}*\nStatus: *{{ $json.price_status.replace(/_/g, ' ') }}*\n\n\u2022 *Plan:* {{ $json.plan_name }} ({{ $json.billing_cycle }})\n\u2022 *Change:* {{ $json.price_change_percent }}% (From ${{ $json.old_price_numeric }} to ${{ $json.current_price_numeric }})\n\n---\nView Details: <{{ $('Loop Monitor Each Plan').item.json.URL }}>",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09UEF9P02Y",
"cachedResultName": "competitors-monitoring"
},
"otherOptions": {
"mrkdwn": true,
"includeLinkToWorkflow": false
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "61c3d905-0441-47a9-9421-54be236100c1",
"name": "Sheets: Get Competitor List",
"type": "n8n-nodes-base.googleSheets",
"position": [
80,
0
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/150NeYyp726AQEs-Rt61hLSDKOAXU0kKDiwF_0De7Fww/edit#gid=0",
"cachedResultName": "competitors"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "150NeYyp726AQEs-Rt61hLSDKOAXU0kKDiwF_0De7Fww",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/150NeYyp726AQEs-Rt61hLSDKOAXU0kKDiwF_0De7Fww/edit?usp=drivesdk",
"cachedResultName": "Competitors"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "9bc391a1-28bd-462e-b026-cf44d2cde6c0",
"name": "Code: Filter HTML Noise",
"type": "n8n-nodes-base.code",
"position": [
944,
16
],
"parameters": {
"jsCode": "// Function is set to \"Run Once for All Items\"\n\nconst rawHtml = $input.first().json.results[0].content; // Assuming this is the raw HTML output\n\n// 1. Aggressively strip known boilerplate tags and content (CSS, Scripts, HTML headers)\nlet textContent = rawHtml\n .replace(/<script\\b[^>]*>([\\s\\S]*?)<\\/script>/gi, '') // Remove script blocks\n .replace(/<style\\b[^>]*>([\\s\\S]*?)<\\/style>/gi, '') // Remove style blocks\n .replace(/<head\\b[^>]*>([\\s\\S]*?)<\\/head>/gi, '') // Remove header blocks\n .replace(/<\\/?[^>]+(>|$)/g, \"\") // Remove all remaining HTML tags\n .toUpperCase(); // Convert to uppercase for case-insensitive keyword searching\n\n// 2. Define Universal Slicing Markers (Based on commercial page structure)\nconst startMarkers = ['PRODUCTS', 'FEATURES', 'PRICING', 'FREE FOREVER'];\nconst endMarkers = ['FREQUENTLY ASKED QUESTIONS', 'FAQ', 'LEGAL', 'TERMS OF SERVICE', 'ABOUT US', 'COMPANY'];\n\nlet effectiveStart = 0;\nlet effectiveEnd = textContent.length;\n\n// 3. Find START (First Product/Pricing Section)\nfor (const marker of startMarkers) {\n let index = textContent.indexOf(marker);\n if (index !== -1) {\n effectiveStart = index;\n break;\n }\n}\n\n// 4. Find END (Last Footer/Legal Section)\nfor (const marker of endMarkers) {\n let index = textContent.indexOf(marker);\n if (index !== -1 && index > effectiveStart + 100) { // Add buffer\n effectiveEnd = index;\n break;\n }\n}\n\nconst isolatedPricingSection = textContent.slice(effectiveStart, effectiveEnd).trim();\n\n// 5. Output the super-clean text\nreturn [{\n json: {\n clean_pricing_text: isolatedPricingSection,\n status: isolatedPricingSection.length > 500 ? 'SUCCESS' : 'FAILURE: Content Too Short'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "4b755a28-f4ce-4182-b032-37c1a02a9e9b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1568,
-608
],
"parameters": {
"width": 688,
"height": 832,
"content": "# AI-Powered Price Watchdog: Competitor Monitoring & Alerting (Decodo & Gemini)\n\n## How It Works\nThis advanced workflow automates market monitoring by intelligently extracting structured pricing data from dynamic competitor websites. It uses a scheduled trigger to run the following loop for every competitor URL:\n\n1. **Dynamic Scraping:** **Decodo** fetches the complete, fully-rendered HTML (JavaScript must be ON).\n2. **AI Structuring:** **Gemini** analyzes the raw text and extracts all pricing plans into a rigid JSON array, effectively bypassing brittle CSS selectors.\n3. **Comparison & Alerting:** A **Code Node** parses the current prices against the historical prices stored in Google Sheets. It calculates the percentage change, handles critical **\"Free-to-Paid\"** edge cases, and assigns an `ALERT` status if the change exceeds the global threshold.\n4. **Conditional Delivery:** The workflow sends an instant **Slack notification** only for the plans flagged as `ALERT`.\n5. **State Management:** The final update shifts the current price data to the `Old Plans` column, setting the baseline for the next scheduled comparison run.\n\nThe system features robust error handling, ensuring data integrity even if the AI output fails or if historical data is missing.\n\n## Setup Steps\n1. **Credentials:** Obtain API keys for **Decodo**, **Google Sheets**, and **Slack**.\n2. **Sheets Setup:** Create a Google Sheet with the required columns (`Name`, `URL`, `Old Plans`, `Last Plans`, `Updated At`) to serve as the historical database.\n3. **Decodo Node Installation:** The Decodo node must be available in your n8n canvas. Search for and install the **`Decodo`** node if necessary.\n4. **Global Configuration:** Set your `alert_threshold` (e.g., `10`) in the **`Config: Alert Parameters`** node.\n"
},
"typeVersion": 1
},
{
"id": "f4d8adbd-aed4-4495-b82b-5b731c57bb9f",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-800,
112
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "3d9347b2-cf0c-4cb8-bd3e-77ad828a61e0",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
656,
-160
],
"parameters": {
"color": 7,
"width": 464,
"height": 384,
"content": "## Full HTML Scraping & Filtering\n**Decodo** scrapes dynamic HTML (JSON). Code then slices the text using universal keywords to isolate the pricing tables."
},
"typeVersion": 1
},
{
"id": "ea48ff7d-e25b-427e-8c89-e4d384958822",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
656,
-400
],
"parameters": {
"color": 3,
"width": 464,
"height": 208,
"content": "## \ud83c\udf81 Exclusive 80% Discount!\n\nGet **80% OFF** the **23k Advanced Scraping API** plan at Decodo using this workflow.\n\n**Coupon Code:** `ATTAN8N`\n\n\ud83d\udc49 [**Click here to Sign Up & Claim**](https://visit.decodo.com/c/6679292/3071239/17480)\n"
},
"typeVersion": 1
},
{
"id": "72a4e016-8177-4a16-941a-b38e04a2f31c",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-624,
-160
],
"parameters": {
"color": 7,
"width": 464,
"height": 384,
"content": "## Global Configuration\nThis node centrally defines global parameters, including the **`alert_threshold_percent`**, for the entire monitoring process."
},
"typeVersion": 1
},
{
"id": "6bf5c2b0-8954-4f81-bd9c-d7db8ff6c4c7",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-96,
-160
],
"parameters": {
"color": 7,
"width": 448,
"height": 384,
"content": "## Data Sourcing\nThis node retrieves the **live list of competitor URLs** directly from your **Google Sheet** using the **Get Row(s)** action."
},
"typeVersion": 1
},
{
"id": "060eb55c-fe56-4950-b4df-45d34b4c4265",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1184,
-160
],
"parameters": {
"color": 7,
"width": 544,
"height": 624,
"content": "## AI Extraction & Structuring\nThe AI acts as a **Data Analyst**, structuring cleaned text into a rigid **JSON Schema** of pricing plans and features."
},
"typeVersion": 1
},
{
"id": "313e798f-f7fc-4dbc-b4c3-388aca1d9012",
"name": "If: Price Parsed Successfully?",
"type": "n8n-nodes-base.if",
"position": [
1936,
16
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "563ab06d-bc77-4d40-a3ad-183380535246",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.error }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "cbb1cee9-ee2c-4f5b-be25-2bd69213cf9c",
"name": "If: History Exists?",
"type": "n8n-nodes-base.if",
"position": [
2176,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6499381c-65bf-4af5-9506-32f25d46f7d1",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $('Loop Monitor Each Plan').item.json['Last Plans'] }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "bc03be94-860c-490b-93da-1fdb85552986",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1792,
-176
],
"parameters": {
"color": 7,
"width": 608,
"height": 400,
"content": "## Error Guard & History Check\nFinal validation checks if the price is clean, and the history exists to decide between **comparison** or **initial logging**."
},
"typeVersion": 1
},
{
"id": "bf207757-92af-4a27-b995-874c2e34443a",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
2464,
-416
],
"parameters": {
"color": 7,
"width": 576,
"height": 512,
"content": "## Comparison & Conditional Alerting\n**Code: Calculate Price Diff** finds changes, handles the **free-to-paid** case, and checks the **alert threshold** before splitting notifications."
},
"typeVersion": 1
},
{
"id": "4c07dbc2-e8bd-4350-bad3-ba401b377761",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
3104,
-416
],
"parameters": {
"color": 7,
"width": 848,
"height": 640,
"content": "## Final Logging & Alert Delivery\nShifts historical price data, logs new prices, and sends urgent Slack alerts for any significant change detected."
},
"typeVersion": 1
},
{
"id": "a1943de4-90c3-4082-b1c9-d167d0199150",
"name": "Loop Monitor Each Plan",
"type": "n8n-nodes-base.splitInBatches",
"position": [
448,
0
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "6240f365-c365-4894-ba2e-f88abdfb19ba",
"name": "AI Agent: Extract Plans",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
1344,
16
],
"parameters": {
"text": "=Raw Pricing Page HTML:\n{{ $json.clean_pricing_text }}\n\n",
"options": {
"systemMessage": "=You are an expert pricing analyst and data extractor. Your task is to analyze the raw HTML content provided below for a competitor's pricing page.\n\n**CRITICAL RULE: You MUST exhaustively search the entire input and return a list of ALL pricing plans found, even if there are dozens.**\n\nYou must identify all distinct pricing tiers/plans, their price, and billing cycle.\n\nYour output must contain ONLY a single, valid JSON array that adheres strictly to the following schema. Do not include any introductory or concluding remarks outside the JSON structure. If no clear pricing plans are found, return an empty array [].\n\nYou must identify all distinct pricing tiers/plans, their price, and billing cycle.\n\nIgnore all script tags, CSS links, and header/footer boilerplate. Focus only on the main pricing section.\n\nRequired JSON Schema:\n\n[\n {\n \"plan_name\": \"string (e.g., Basic, Pro, Enterprise)\",\n \"price_usd\": \"string (monetary value, e.g., $99.00)\",\n \"billing_cycle\": \"string (e.g., monthly, annual, one-time)\",\n \"key_feature_summary\": \"string (A concise, 10-word summary of the plan's main value)\"\n }\n]"
},
"promptType": "define",
"hasOutputParser": true
},
"retryOnFail": true,
"typeVersion": 3
},
{
"id": "100c8167-8672-4afa-b25f-d75d52980747",
"name": "Code: Price Diff & Filter",
"type": "n8n-nodes-base.code",
"position": [
2608,
-160
],
"parameters": {
"jsCode": "// This Code Node must be set to run ONCE for ALL ITEMS,\n// as the input is typically the full array of current plans.\n\n// 1. Get the current and historical data from the merged input item:\nconst currentPlans = $input.first().json.output || [];\nconst historicalRow = $('Loop Monitor Each Plan').first(); // Data from Sheets Read node\n\nconst lastPlansJson = historicalRow.json['Last Plans'] || '[]';\n\nconst alertItems = []; // Array to hold ONLY the plans requiring an alert\n\ntry {\n // 2. Parse the historical JSON string into a usable array of objects\n const lastPlans = JSON.parse(lastPlansJson); \n\n const diffs = [];\n\n // Function to calculate the difference for a single plan\n const getPlanDiff = (currentPlan, lastPlans) => {\n\n // Find the historical record that matches the current plan name and billing cycle\n const oldPlan = lastPlans.find(p => \n p.plan_name === currentPlan.plan_name && \n p.billing_cycle === currentPlan.billing_cycle\n );\n \n // Ensure prices are numeric (already done in the previous Code node, but we'll re-parse the old plan string for safety)\n const currentPriceString = currentPlan.price_usd;\n \n const oldPriceString = oldPlan ? oldPlan.price_usd : null;\n \n // Clean the old price string (remove $, text) and convert to number\n \n\n const oldPrice = oldPriceString.toUpperCase().includes('FREE')\n ? 0 \n : parseFloat(oldPriceString.replace(/[^0-9.]/g, ''));\n\n\n const currentPrice = currentPriceString.toUpperCase().includes('FREE')\n ? 0 \n : parseFloat(currentPriceString.replace(/[^0-9.]/g, ''));\n\n \n let priceDiff = null;\n let priceStatus='';\n\n if (currentPrice !== null && oldPrice !== null) {\n \n if (oldPrice === 0 && currentPrice > 0) {\n // Case 1: FREE to PAID (Division by Zero Scenario)\n priceDiff = 9999; // Assign a high value to force an alert\n priceStatus = 'FREE_TO_PAID_ALERT';\n } else if (oldPrice > 0) {\n // Case 2: Standard Paid-to-Paid Calculation\n priceDiff = ((currentPrice - oldPrice) / oldPrice) * 100;\n priceStatus = priceDiff > 0 ? 'PRICE_INCREASE' : 'PRICE_DECREASE';\n } else if (oldPrice > 0 && currentPrice === 0) {\n // Case 3: Paid-to-FREE (oldPrice > 0 and currentPrice === 0)\n priceDiff = -100;\n priceStatus = 'PAID_TO_FREE_ALERT';\n }\n }\n\n return {\n ...currentPlan, // Keep all current plan details\n old_price_numeric: oldPrice,\n current_price_numeric: currentPrice,\n price_change_percent: priceDiff,\n price_status: priceStatus,\n alert_status: (priceDiff !== null && Math.abs(priceDiff) >= $('Config: Alert Parameters').first().json.alert_threshold_percent) \n ? 'ALERT' \n : 'NO_CHANGE',\n };\n };\n\n // 3. Iterate and calculate diffs for all plans\n for (const plan of currentPlans) {\n const diffPlan = getPlanDiff(plan, lastPlans);\n diffs.push(diffPlan);\n\n // 2. CRUCIAL FILTER: Only push items that require immediate action\n if (diffPlan.alert_status === 'ALERT') { \n alertItems.push(diffPlan);\n }\n }\n\n return [{ json: { price_diffs: diffs,\n alert_items: alertItems} }];\n\n} catch (e) {\n // Handle JSON parsing error if the old 'Last Plans' column was corrupted\n return [{ json: { error: \"JSON_PARSE_ERROR\", message: e.message } }];\n}"
},
"typeVersion": 2
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "Config: Alert Parameters",
"type": "main",
"index": 0
}
]
]
},
"Discard Error Item": {
"main": [
[
{
"node": "Loop Monitor Each Plan",
"type": "main",
"index": 0
}
]
]
},
"If: Alerts to Send?": {
"main": [
[
{
"node": "Split Alerts for Notification",
"type": "main",
"index": 0
}
],
[
{
"node": "Update row in sheet",
"type": "main",
"index": 0
}
]
]
},
"If: History Exists?": {
"main": [
[
{
"node": "Code: Price Diff & Filter",
"type": "main",
"index": 0
}
],
[
{
"node": "Update row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Update row in sheet": {
"main": [
[
{
"node": "Loop Monitor Each Plan",
"type": "main",
"index": 0
}
]
]
},
"Loop Monitor Each Plan": {
"main": [
[],
[
{
"node": "Decodo: Fetch Full HTML",
"type": "main",
"index": 0
}
]
]
},
"AI Agent: Extract Plans": {
"main": [
[
{
"node": "If: Price Parsed Successfully?",
"type": "main",
"index": 0
}
]
]
},
"Code: Filter HTML Noise": {
"main": [
[
{
"node": "AI Agent: Extract Plans",
"type": "main",
"index": 0
}
]
]
},
"Decodo: Fetch Full HTML": {
"main": [
[
{
"node": "Code: Filter HTML Noise",
"type": "main",
"index": 0
}
]
]
},
"Config: Alert Parameters": {
"main": [
[
{
"node": "Sheets: Get Competitor List",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent: Extract Plans",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "AI Agent: Extract Plans",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Code: Price Diff & Filter": {
"main": [
[
{
"node": "If: Alerts to Send?",
"type": "main",
"index": 0
}
]
]
},
"Send Competitor Price Alert": {
"main": [
[
{
"node": "Update row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Sheets: Get Competitor List": {
"main": [
[
{
"node": "Loop Monitor Each Plan",
"type": "main",
"index": 0
}
]
]
},
"Start Workflow (Manual Run)": {
"main": [
[
{
"node": "Config: Alert Parameters",
"type": "main",
"index": 0
}
]
]
},
"Split Alerts for Notification": {
"main": [
[
{
"node": "Send Competitor Price Alert",
"type": "main",
"index": 0
}
]
]
},
"If: Price Parsed Successfully?": {
"main": [
[
{
"node": "If: History Exists?",
"type": "main",
"index": 0
}
],
[
{
"node": "Discard Error Item",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
decodoApigooglePalmApigoogleSheetsOAuth2ApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Never miss a competitor price change again.
Source: https://n8n.io/workflows/11063/ — 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.
CV → Match → Screen → Decide, all automated
This workflow is ideal for venture capitalists, sales teams, or market researchers who need to automatically track and compile lists of recently funded companies.
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
This workflow is essential for product managers, marketing teams, and founders who need to quickly gather and distill actionable insights from competitor launches to inform their own product strategy