AutomationFlowsData & Sheets › Monitor Competitor Pricing with Firecrawl, Anthropic, Airtable and Slack

Monitor Competitor Pricing with Firecrawl, Anthropic, Airtable and Slack

ByMychel Garzon @mychel-garzon on n8n.io

This workflow runs every 6 hours to monitor competitor product pages stored in Airtable, scrape current price and stock with Firecrawl, extract structured pricing with Anthropic, send priority-based alerts to Slack, and log changes to Google Sheets while syncing the latest…

Cron / scheduled trigger★★★★☆ complexity19 nodesAirtableSlackGoogle Sheets@Mendable/N8N Nodes Firecrawl
Data & Sheets Trigger: Cron / scheduled Nodes: 19 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #15893 — we link there as the canonical source.

This workflow follows the Airtable → 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 →

Download .json
{
  "id": "kle3yKf31ubePxCW",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Competitor Price Intelligence \u2014 Automated",
  "tags": [],
  "nodes": [
    {
      "id": "f3a311f2-5338-4e05-b3d8-11b24f87090c",
      "name": "Every 6 Hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        2624,
        6432
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7b40dbb9-75e4-4dae-a6c9-5d55be8604c2",
      "name": "Get Competitor List",
      "type": "n8n-nodes-base.airtable",
      "position": [
        2864,
        6432
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appYYYYYY"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblCompetitors"
        },
        "operation": "getAll"
      },
      "typeVersion": 2
    },
    {
      "id": "133a2574-ab4d-48f6-ab7f-0a344633b115",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        3296,
        6432
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "b17bb996-d208-4df7-b3b5-4cf4eda91fa1",
      "name": "Extract Price & Build Delta",
      "type": "n8n-nodes-base.code",
      "position": [
        3920,
        6416
      ],
      "parameters": {
        "jsCode": "const item = $input.first().json;\n\nif (item.success === false || (!item.markdown && !item.content)) {\n  return [{ json: {\n    _skip: true,\n    _skipReason: item.error || 'Firecrawl returned no content',\n    alertPriority: '_skip',\n    metadata: $node['Loop Over Items'].json\n  }}];\n}\n\nconst pageContent = (item.markdown || item.content || '').substring(0, 8000);\n\n// Error handling: wrap Anthropic call so a 429/500 skips this item rather than crashing the loop\nlet response;\ntry {\n  response = await $http.request({\n    method: 'POST',\n    url: 'https://api.anthropic.com/v1/messages',\n    headers: {\n      'x-api-key': $credentials.anthropicApi.apiKey,\n      'anthropic-version': '2023-06-01',\n      'content-type': 'application/json'\n    },\n    body: JSON.stringify({\n      model: 'claude-3-5-sonnet-20241022',\n      max_tokens: 256,\n      tools: [{\n        name: 'extract_pricing',\n        description: 'Extract structured pricing data from product page content.',\n        input_schema: {\n          type: 'object',\n          properties: {\n            price: { type: 'number', description: 'Current product price as a number' },\n            currency: { type: 'string', description: 'ISO 4217 currency code, e.g. EUR, USD' },\n            in_stock: { type: 'boolean', description: 'Whether the product is currently in stock' },\n            stock_count: { type: ['number', 'null'], description: 'Exact stock count if shown, otherwise null' }\n          },\n          required: ['price', 'currency', 'in_stock']\n        }\n      }],\n      tool_choice: { type: 'tool', name: 'extract_pricing' },\n      system: 'You are a pricing data extractor. Always call the extract_pricing tool with values found in the content.',\n      messages: [{\n        role: 'user',\n        content: `Extract the pricing data from this product page content:\\n\\n${pageContent}`\n      }]\n    })\n  });\n} catch (err) {\n  return [{ json: {\n    _skip: true,\n    _skipReason: `Anthropic API error during extraction: ${err.message}`,\n    alertPriority: '_skip',\n    metadata: $node['Loop Over Items'].json\n  }}];\n}\n\nconst toolBlock = (response.content || []).find(b => b.type === 'tool_use');\nconst extracted = toolBlock?.input || {};\n\nconst loopMeta = $node['Loop Over Items'].json;\nconst fields = loopMeta.fields || {};\n\nconst phistorical = fields['Last Price'] ? parseFloat(fields['Last Price']) : 0;\nconst pscraped = extracted.price ? parseFloat(extracted.price) : 0;\n\n// Guard: no valid price means extraction failed\nif (!pscraped || pscraped <= 0) {\n  return [{ json: {\n    _skip: true,\n    _skipReason: 'No valid price extracted \u2014 page structure may have changed',\n    alertPriority: '_skip',\n    metadata: loopMeta\n  }}];\n}\n\nlet delta = 0;\nif (phistorical > 0) {\n  delta = ((pscraped - phistorical) / phistorical) * 100;\n} else if (phistorical === 0 && pscraped > 0) {\n  delta = 100;\n}\n\n// Deduplication: if price and stock status are identical to last run, mark as duplicate.\n// Duplicate items still loop back but skip Slack, Airtable, and Sheets writes entirely.\nconst priceUnchanged = phistorical > 0 && Math.abs(delta) < 0.01;\nconst stockUnchanged = fields['In Stock'] === extracted.in_stock;\nif (priceUnchanged && stockUnchanged) {\n  return [{ json: {\n    _skip: true,\n    _skipReason: 'No change detected \u2014 price and stock status identical to last run',\n    alertPriority: '_skip',\n    metadata: loopMeta\n  }}];\n}\n\nlet priority = 'Info';\nif (delta <= -15) priority = 'Critical';\nelse if (delta <= -5) priority = 'High';\nelse if (fields['In Stock'] === true && extracted.in_stock === false) priority = 'Medium';\nelse if (phistorical === 0 && pscraped > 0) priority = 'Medium';\nelse if (delta > 0) priority = 'Info';\n\nreturn [{ json: {\n  _skip: false,\n  recordId: loopMeta.id,\n  competitor: fields['Competitor Name'],\n  productName: fields['Product Name'],\n  productUrl: fields['Product URL'],\n  currentPrice: pscraped,\n  historicalPrice: phistorical,\n  priceDeltaPercent: delta,\n  inStock: extracted.in_stock,\n  stockCount: extracted.stock_count ?? null,\n  alertPriority: priority,\n  scannedAt: new Date().toISOString()\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "83c8c9c8-a040-47dc-bc64-a82765ebb266",
      "name": "Priority Switch Router",
      "type": "n8n-nodes-base.switch",
      "position": [
        4192,
        6416
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "",
                    "rightValue": ""
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.1
    },
    {
      "id": "d67375d8-7953-429b-af9b-cb2508055417",
      "name": "Generate Strategy",
      "type": "n8n-nodes-base.code",
      "position": [
        2400,
        5792
      ],
      "parameters": {
        "jsCode": "const item = $input.first().json;\n\nlet response;\ntry {\n  response = await $http.request({\n    method: 'POST',\n    url: 'https://api.anthropic.com/v1/messages',\n    headers: {\n      'x-api-key': $credentials.anthropicApi.apiKey,\n      'anthropic-version': '2023-06-01',\n      'content-type': 'application/json'\n    },\n    body: JSON.stringify({\n      model: 'claude-3-5-sonnet-20241022',\n      max_tokens: 512,\n      temperature: 0.3,\n      system: 'You are a competitive pricing strategist. Be concise and actionable.',\n      messages: [{\n        role: 'user',\n        content: `Analyze this pricing event and return 3-5 tactical bullet-point recommendations.\\n\\nCompetitor: ${item.competitor}\\nProduct: ${item.productName}\\nOld Price: ${item.historicalPrice}\\nNew Price: ${item.currentPrice}\\nDelta: ${item.priceDeltaPercent.toFixed(2)}%`\n      }]\n    })\n  });\n} catch (err) {\n  // Strategy generation failed \u2014 still send Slack alert but without recommendations\n  return [{ json: {\n    ...item,\n    strategy: `\u26a0\ufe0f Strategy generation failed: ${err.message}. Review manually.`\n  }}];\n}\n\nconst strategy = response.content?.[0]?.text || 'No strategy generated.';\nreturn [{ json: { ...item, strategy } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "d3895e41-fa5d-4dbe-9075-ceff9f6d387e",
      "name": "Immediate Slack Notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        2720,
        5792
      ],
      "parameters": {
        "text": "=\ud83d\udea8 *CRITICAL PRICE MOVEMENT DETECTED*\n\n*Competitor:* {{$json.competitor}}\n*Product:* {{$json.productName}}\n*Old Price:* {{$json.historicalPrice}}\n*New Price:* {{$json.currentPrice}}\n*Price Drop:* {{$json.priceDeltaPercent.toFixed(1)}}%\n*Countermeasures:*\n{{$json.strategy}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "C08AAAAAA"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "382de9f7-32ea-4344-866d-76271e34df8e",
      "name": "Standard Slack Log",
      "type": "n8n-nodes-base.slack",
      "position": [
        2720,
        5952
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f *Standard Stock/Price Update*\n*Competitor:* {{$json.competitor}}\n*Product:* {{$json.productName}} reached Medium priority threshold.\n*Current Price:* {{$json.currentPrice}}\n*In Stock:* {{$json.inStock}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "C08AAAAAA"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "10f88593-38f7-4e3d-86ae-a87f3b6d81ae",
      "name": "Info Log Only",
      "type": "n8n-nodes-base.code",
      "position": [
        2720,
        6128
      ],
      "parameters": {
        "jsCode": "// Info priority \u2014 no Slack alert, pass through to write nodes\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "f344f547-487c-4b9d-a7a3-0d0ead2014d1",
      "name": "Drop Skipped Item",
      "type": "n8n-nodes-base.code",
      "position": [
        2944,
        6128
      ],
      "parameters": {
        "jsCode": "// Pass skipped item back to loop to trigger next iteration\n// Bypasses Airtable/Sheets logging for failed pages\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "1155c0af-68db-43b0-a007-d263d0324ddc",
      "name": "Synchronize Active State",
      "type": "n8n-nodes-base.airtable",
      "position": [
        3216,
        5888
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appYYYYYY"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblCompetitors"
        },
        "options": {},
        "operation": "update"
      },
      "typeVersion": 2
    },
    {
      "id": "ce1632f1-336f-4e85-a8bd-23cd43669567",
      "name": "Append Historical Sheet Log",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3456,
        5888
      ],
      "parameters": {
        "columns": {
          "value": {
            "Price": "={{$json.currentPrice}}",
            "Product": "={{$json.productName}}",
            "Change %": "={{Number($json.priceDeltaPercent.toFixed(2))}}",
            "Timestamp": "={{$json.scannedAt}}",
            "Competitor": "={{$json.competitor}}",
            "Alert Priority": "={{$json.alertPriority}}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "REPLACE_WITH_YOUR_SHEET_TAB_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "f45d654f-9b98-4aa9-9869-7161b6ea14fb",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1712,
        5648
      ],
      "parameters": {
        "width": 592,
        "height": 960,
        "content": "## Competitor Price Intelligence\n\n### Scheduled Competitor Price Monitoring with AI Alerts\n\n### How it works\nThis workflow runs on a 6-hour schedule and monitors competitor product pages\nfor price and stock changes, routing alerts by urgency to Slack and logging\nall events to Airtable and Google Sheets.\n\n**The Monitoring Pipeline**\n1. Triggered automatically every 6 hours via a Schedule node.\n2. Fetches the full competitor list from Airtable (`tblCompetitors`), with\n   each record containing a Product URL, Last Price, and In Stock status.\n3. Loops over each competitor one at a time using SplitInBatches.\n4. Scrapes the live product page via Firecrawl and extracts structured pricing\n   data using the Anthropic API with tool use (`extract_pricing`).\n5. Calculates the percentage delta vs the stored historical price and applies\n   deduplication \u2014 items with no change in price or stock are skipped entirely.\n6. Routes each item by alert priority: Critical, High, Medium, Info, or _skip.\n7. Critical / High: generates 3-5 tactical countermeasures via AI, then posts\n   an immediate Slack alert.\n8. Medium: posts a lighter Slack notification. No strategy generated.\n9. Info: passes through silently to the write nodes. No Slack alert sent.\n10. _skip: returns directly to the loop. No Airtable or Sheets write occurs.\n11. All actionable events update the Airtable record and append a timestamped\n    row to Google Sheets for historical trend tracking.\n\n### Setup steps\n- [ ] Replace `appYYYYYY` in the Airtable nodes with your actual Base ID.\n- [ ] Replace `tblCompetitors` with your actual Table ID.\n- [ ] Set the Slack channel ID in both Slack nodes (`C08AAAAAA` placeholder).\n- [ ] Replace `REPLACE_WITH_YOUR_GOOGLE_SHEET_ID` with your Sheet ID.\n- [ ] Replace `REPLACE_WITH_YOUR_SHEET_TAB_NAME` with your tab name.\n- [ ] Connect your Firecrawl, Anthropic API, Airtable, Slack, and Google\n      Sheets credentials to the relevant nodes.\n\n### Required credentials\n- **Firecrawl API** (product page scraping)\n- **Anthropic API** (pricing extraction + strategy generation)\n- **Airtable** (competitor list read + record updates)\n- **Slack** (alert notifications)\n- **Google Sheets** (historical log appends)\n\n### Airtable table fields expected\n`Competitor Name` \u00b7 `Product Name` \u00b7 `Product URL` \u00b7 `Last Price` \u00b7 `In Stock`"
      },
      "typeVersion": 1
    },
    {
      "id": "45953811-e4ef-4a6b-9be8-662e2bfb588c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2352,
        6304
      ],
      "parameters": {
        "color": 7,
        "width": 736,
        "height": 304,
        "content": "### Trigger & Data Fetch\nFires the monitoring cycle on a 6-hour schedule and pulls the full competitor\nlist from Airtable before passing it to the loop."
      },
      "typeVersion": 1
    },
    {
      "id": "bac89367-2156-4d6e-9bb4-d33b01c29a29",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3120,
        6304
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 304,
        "content": "### Loop & Scrape\nIterates over each competitor record one at a time and scrapes the live\nproduct page via Firecrawl, returning the page content as Markdown."
      },
      "typeVersion": 1
    },
    {
      "id": "052b9071-4e10-4fe2-a6e6-b3d478fce206",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3792,
        6304
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 304,
        "content": "### AI Extraction & Routing\nCalls the Anthropic API via tool use to extract price, currency, and stock status. Calculates the price delta, applies deduplication, assigns an alert priority, and branches the flow accordingly."
      },
      "typeVersion": 1
    },
    {
      "id": "9256b926-5274-4910-a7ef-d12e43106a76",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2352,
        5648
      ],
      "parameters": {
        "color": 7,
        "width": 736,
        "height": 624,
        "content": "### Alert Handling\nGenerates AI countermeasure recommendations for critical price drops and posts Slack alerts at the appropriate urgency level for each priority tier. \nSkipped items are returned to the loop without writing to any data store."
      },
      "typeVersion": 1
    },
    {
      "id": "3b4a0ef3-d751-4bd4-beb6-6956fb80c7f4",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3120,
        5648
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 624,
        "content": "### Sync & Historical Logging\nUpdates the Airtable record with the latest price and stock status, then appends a timestamped row to Google Sheets for trend tracking over time."
      },
      "typeVersion": 1
    },
    {
      "id": "daed8a28-4b11-4ace-ab4f-0460e8578055",
      "name": "Scrape Product Page",
      "type": "@mendable/n8n-nodes-firecrawl.firecrawl",
      "position": [
        3584,
        6416
      ],
      "parameters": {
        "url": "={{$json.fields['Product URL']}}",
        "operation": "scrape",
        "requestOptions": {}
      },
      "credentials": {
        "firecrawlApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "2752dd5c-6d58-48a7-b3ef-93852cdbc195",
  "connections": {
    "Every 6 Hours": {
      "main": [
        [
          {
            "node": "Get Competitor List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Info Log Only": {
      "main": [
        [
          {
            "node": "Synchronize Active State",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Scrape Product Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Drop Skipped Item": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Strategy": {
      "main": [
        [
          {
            "node": "Immediate Slack Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Standard Slack Log": {
      "main": [
        [
          {
            "node": "Synchronize Active State",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Competitor List": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Product Page": {
      "main": [
        [
          {
            "node": "Extract Price & Build Delta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Synchronize Active State": {
      "main": [
        [
          {
            "node": "Append Historical Sheet Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Historical Sheet Log": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Price & Build Delta": {
      "main": [
        [
          {
            "node": "Priority Switch Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Immediate Slack Notification": {
      "main": [
        [
          {
            "node": "Synchronize Active State",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow runs every 6 hours to monitor competitor product pages stored in Airtable, scrape current price and stock with Firecrawl, extract structured pricing with Anthropic, send priority-based alerts to Slack, and log changes to Google Sheets while syncing the latest…

Source: https://n8n.io/workflows/15893/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

This n8n workflow automates the end-to-end client onboarding process: capturing client details, validating emails, assigning tiers, generating welcome packs, creating tasks, notifying teams, archiving

Google Sheets, Gmail, Airtable +5
Data & Sheets

This workflow automatically creates a weekly report of tasks. Every Friday morning, it collects all task details from Airtable, checks progress, and prepares a summary. It also highlights important or

Gmail, Slack, Google Sheets +2
Data & Sheets

This guide will walk you through setting up your n8n workflow. By the end, you'll have a fully automated system for managing your recruitment pipeline.

Google Calendar Trigger, Slack, HTTP Request +4
Data & Sheets

This workflow automates tax compliance by aggregating multi-channel revenue data, calculating jurisdiction-specific tax obligations, detecting anomalies, and generating submission-ready reports for ta

Gmail, Google Sheets, Airtable +1
Data & Sheets

Founders, product managers, content strategists, indie hackers, and anyone who wants to automatically monitor tech industry trends across multiple sources — without manually browsing Hacker News and P

HTTP Request, Google Sheets, Slack +1