{
  "id": "y0Yk7da21T4u9zlp",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Product Price Monitor with Mailgun and MongoDB",
  "tags": [],
  "nodes": [
    {
      "id": "508619bd-1067-4baa-bf4c-502b9334c380",
      "name": "Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -128
      ],
      "parameters": {
        "width": 550,
        "height": 690,
        "content": "## How it works\n\nThis webhook-driven workflow turns a simple POST request into a complete price-monitoring pipeline. When your external system posts a JSON body that contains a list of product URLs together with the prices you currently expect, the Webhook node starts the sequence. A short Code node validates the payload and converts every product entry into its own n8n item so downstream nodes can work in parallel. Each item is passed to ScrapeGraphAI, which loads the product page and extracts live information such as name, current price, currency, availability and seasonal tags. The original expected price is merged back in and a calculation node determines the percentage change. An IF node then checks whether the drop is larger than ten percent. Large drops immediately trigger a Slack alert, while normal changes are quietly written to Jira for later analysis and weekly reports.\n\n## Setup steps\n\n1. Add your ScrapeGraphAI API credentials in the scraper node  \n2. Connect your Slack account and select the alert channel  \n3. Connect your Jira cloud credentials and set the correct project key  \n4. Point your system or test tool to the webhook URL shown in the trigger  \n5. Send a sample payload with url and expectedPrice fields  \n6. Adjust the price-drop threshold in the IF node if necessary  \n7. Activate the workflow and monitor Slack for alerts"
      },
      "typeVersion": 1
    },
    {
      "id": "f750bfaa-d72c-487c-a8c8-26cb60e72840",
      "name": "Section \u2013 Intake",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        576,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 530,
        "height": 846,
        "content": "## Webhook & Preparation\n\nThe nodes underneath this sticky handle everything that happens the moment data enters the system. The Webhook node listens on the /product-price-monitor path and accepts POST requests with a body shaped like `{ \"products\": [ { \"url\": \"\u2026\", \"expectedPrice\": 19.99 } ] }`. A dedicated Code node immediately validates that structure, throwing a gentle but clear error if the caller forgets the products array or leaves it empty. Once validated, the same node flattens the array so that each product becomes its own item, adding both `url` and `expectedPrice` fields. Doing this transformation right at the perimeter means every downstream node can assume a clean, predictable shape, avoiding defensive checks later. Because all validation sits here, expanding the payload\u2014perhaps with SKU, brand or campaign tags\u2014only requires touching this single node, keeping maintenance low and future-proofing the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "b83611c1-6803-41a1-ad69-327552e36dbb",
      "name": "Section \u2013 Scraping & Data",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1168,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 450,
        "height": 846,
        "content": "## Scraping & Enrichment\n\nAfter the product list is split, each item flows through a controlled scraping lane. The SplitInBatches node allows us to throttle the rate at which we hit ScrapeGraphAI, protecting us from both platform limits and target-site bans. ScrapeGraphAI then renders the product page, processes dynamic content and returns a neat JSON bundle that includes name, live price, currency, availability and any seasonal keywords it can infer. Immediately afterwards a Merge node combines this fresh data with the original `expectedPrice` so we keep historical context. A small Code node converts the price string to a float, calculates the percentage delta and appends helper properties such as `priceChangePercent`. Grouping all enrichment here keeps later branches clean and makes it simple to slot in future calculations\u2014think profit margin or competitor comparison\u2014without touching alert or storage logic."
      },
      "typeVersion": 1
    },
    {
      "id": "675a11f1-4478-4dc1-97ec-4483ced78c34",
      "name": "Section \u2013 Logic",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1648,
        -112
      ],
      "parameters": {
        "color": 7,
        "width": 450,
        "height": 814,
        "content": "## Decision Logic\n\nThis compact but critical block decides whether the workflow should shout or simply log. The IF node checks if `priceChangePercent` is smaller than \u201110, meaning the live price is at least ten percent lower than expected. Because the calculation happens earlier we only need a single numerical rule here, but you can extend the same node with additional conditions\u2014maybe the `season` equals \u201cSummer\u201d or the product is marked as low stock. Isolating the logic in one node prevents alert fatigue and makes the workflow easy to tune without touching scraping or output code. Need a more nuanced policy? Just chain another IF node or switch to a Switch node while everything upstream and downstream continues to work untouched."
      },
      "typeVersion": 1
    },
    {
      "id": "7967ecb6-19c3-48db-9f0c-d4ab23ac2710",
      "name": "Section \u2013 Outputs",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2112,
        -112
      ],
      "parameters": {
        "color": 7,
        "width": 450,
        "height": 798,
        "content": "## Alerts & Storage Outputs\n\nThe top branch converts urgent items into actionable Slack messages. A Set node builds a concise sentence that highlights the product name, live price, delta percentage and a clickable link, then hands it to the Slack node which posts directly into your chosen channel for instant visibility. The lower branch quietly files every routine item as a Jira Task so analysts have a single place to review weekly patterns. Another Set node maps data into `summary` and `description` fields before the Jira node creates the issue inside the project key you specify. Because both branches draw from the same enriched dataset you can reroute either path\u2014email, database, BI warehouse\u2014without rewiring earlier sections. Keeping outputs together makes the end points obvious at a glance and simplifies maintenance."
      },
      "typeVersion": 1
    },
    {
      "id": "ecf9cc25-b44b-47ca-8252-f578804da333",
      "name": "Incoming Product List",
      "type": "n8n-nodes-base.webhook",
      "position": [
        608,
        400
      ],
      "parameters": {
        "path": "product-price-monitor",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 1
    },
    {
      "id": "f476255d-7c3d-4fbf-a9c3-4473e0885da5",
      "name": "Validate & Prepare Data",
      "type": "n8n-nodes-base.code",
      "position": [
        800,
        400
      ],
      "parameters": {
        "jsCode": "// Expect body: { products: [ { url: '', expectedPrice: 0 } ] }\nconst body = $json;\nif (!body.products || !Array.isArray(body.products) || body.products.length === 0) {\n  throw new Error('Request body must contain a products array with at least one element');\n}\nreturn body.products.map(p => ({ json: { url: p.url, expectedPrice: p.expectedPrice } }));"
      },
      "typeVersion": 2
    },
    {
      "id": "2c6b8fce-d884-49e5-a98e-327d7589c17c",
      "name": "Iterate Product List",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1008,
        400
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "d6ad4ad5-5798-482a-a556-eaa89c02378c",
      "name": "Scrape Product Page",
      "type": "n8n-nodes-scrapegraphai.scrapegraphAi",
      "position": [
        1200,
        256
      ],
      "parameters": {
        "userPrompt": "Extract the product name, current price, currency, availability status, category, and any seasonal tags (e.g., summer, winter) from this product page. Respond as JSON: {\"name\": \"string\", \"price\": \"number\", \"currency\": \"string\", \"availability\": \"string\", \"season\": \"string\"}",
        "websiteUrl": "={{ $json.url }}"
      },
      "typeVersion": 1
    },
    {
      "id": "c78a8236-ecd1-4746-9a1f-c09b8504b511",
      "name": "Merge Scraped & Expected",
      "type": "n8n-nodes-base.merge",
      "position": [
        1408,
        400
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "mergeByFields": {
          "values": [
            {}
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "bacd991e-c747-4a90-af1c-9819594055f7",
      "name": "Compute Price Change",
      "type": "n8n-nodes-base.code",
      "position": [
        1664,
        400
      ],
      "parameters": {
        "jsCode": "const data = $json;\nconst price = parseFloat(data.price);\nconst expected = parseFloat(data.expectedPrice);\nlet change = null;\nif (!isNaN(price) && !isNaN(expected) && expected !== 0) {\n  change = ((price - expected) / expected) * 100;\n}\nreturn [{ json: { ...data, price: price, expectedPrice: expected, priceChangePercent: change } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "0879a852-87de-410c-a4e1-b43e119bcfbf",
      "name": "Significant Price Drop?",
      "type": "n8n-nodes-base.if",
      "position": [
        1856,
        400
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.priceChangePercent }}",
              "value2": -10,
              "operation": "smaller"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "dd1fd1ee-f6fd-4268-bf99-aea2df9d28d0",
      "name": "Format Slack Alert",
      "type": "n8n-nodes-base.set",
      "position": [
        2160,
        256
      ],
      "parameters": {
        "options": {
          "dotNotation": true
        }
      },
      "typeVersion": 3
    },
    {
      "id": "a73cf2b2-d714-4684-8f60-64e1d796638d",
      "name": "Send Slack Message",
      "type": "n8n-nodes-base.slack",
      "position": [
        2368,
        256
      ],
      "parameters": {
        "operation": "postMessage"
      },
      "typeVersion": 2
    },
    {
      "id": "d3fc340f-4926-4e01-8828-93a5b5183012",
      "name": "Prepare Jira Issue",
      "type": "n8n-nodes-base.set",
      "position": [
        2224,
        544
      ],
      "parameters": {
        "options": {
          "dotNotation": true
        }
      },
      "typeVersion": 3
    },
    {
      "id": "aeab7b82-78cf-4f36-9009-514e5fa38d69",
      "name": "Create an issue",
      "type": "n8n-nodes-base.jira",
      "position": [
        2368,
        512
      ],
      "parameters": {
        "project": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "issueType": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "additionalFields": {}
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "84bec6eb-1fe7-44e9-af51-b10e08bbed7d",
  "connections": {
    "Format Slack Alert": {
      "main": [
        [
          {
            "node": "Send Slack Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Jira Issue": {
      "main": [
        [
          {
            "node": "Create an issue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Product Page": {
      "main": [
        [
          {
            "node": "Merge Scraped & Expected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute Price Change": {
      "main": [
        [
          {
            "node": "Significant Price Drop?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Iterate Product List": {
      "main": [
        [
          {
            "node": "Scrape Product Page",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Scraped & Expected",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Incoming Product List": {
      "main": [
        [
          {
            "node": "Validate & Prepare Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Significant Price Drop?": {
      "main": [
        [
          {
            "node": "Format Slack Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Jira Issue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate & Prepare Data": {
      "main": [
        [
          {
            "node": "Iterate Product List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Scraped & Expected": {
      "main": [
        [
          {
            "node": "Compute Price Change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}