An article that says “Core costs $9 a month” is correct until the vendor edits one number on its pricing page. From that moment the article is wrong, and every reader who clicks through and sees a different price trusts your next recommendation a little less. Vendors don’t email publishers before they change prices.

The same edit is also news. People searching for that tool want to know what changed, and you can be one of the first pages to say so, with the date and the source.

This guide builds a weekly watcher that fetches the pricing pages you care about, pulls out the price section, compares it with last week’s copy, and sends you the difference. It covers the parts that break naive versions: pages that load prices with JavaScript, A/B tests and regional pricing that create false alarms, where to store snapshots, and how to stay polite.

The short answer

  • Watch once a week, one request per page. Prices rarely change more often, and a weekly check is light on the vendor’s server.
  • Compare the prices, then the wording. Pull every currency amount out of the price section and alert loudly when those change. Alert quietly when only the wording changes.
  • Try a plain fetch first. On November 9, 2026, n8n’s pricing page returned its prices in the raw HTML. Some pages load prices with JavaScript or block automated requests, and those need a headless browser, an official pricing API, or a manual check.
  • Pick the tool you already use. Make Free handles 10 pages for about 150 credits a month. n8n uses about 4 executions a month. A script on GitHub Actions costs nothing and keeps every snapshot in Git history.

What a price change costs an affiliate site

A wrong price does two kinds of damage. The reader who notices it stops trusting the page. And if the change is a price rise or a removed free tier, your “best for beginners” pick may no longer fit the people you recommended it to.

A watcher turns that into a routine. At LeverSolo our rule is that a confirmed price change gets fixed in every affected article the same day, with an updated date. If the change matters to readers, it also gets a short Change Log post that answers three things: what changed and from when, who it affects, and what to do instead, linking the vendor’s own page as the source. The watcher is how the change gets noticed in the first place.

How the watcher works

Every version below follows the same five steps.

  1. Keep a page list. One row per page: a name, the URL, and how to find the price section (a CSS selector, or a start and end phrase such as “Starter” and “Enterprise”).
  2. Fetch each page once.
  3. Extract the price section and clean it: strip the HTML, collapse whitespace, then pull out every currency amount ($9, €20, £12.50).
  4. Compare with last week’s snapshot. Prices differ: high-priority alert. Only wording differs: low-priority note. Nothing differs: stay silent.
  5. Save the new snapshot and send one message listing what changed, before and after.

The hard parts

Pages that load prices with JavaScript

A plain HTTP request downloads the page’s HTML as the server sends it. Many modern pricing pages send a mostly empty shell and fill in prices with JavaScript after it loads in a browser. A plain fetch of those pages finds no prices, so the watcher must raise an alert whenever it can’t find the price section.

You have three options for those pages:

Option How it works Trade-off
Official pricing API or data feed Some vendors publish prices as data. OpenRouter’s public models endpoint, for example, returns a JSON list with each model’s prompt and completion price, and we requested it without an API key on November 9, 2026 The most stable source when it exists. Few vendors offer one
The page’s own data request Open the pricing page with your browser’s developer tools on the Network tab and look for a JSON response containing the prices Undocumented, so it can change without notice, and the vendor’s terms may not allow automated use. Read them first
Headless browser Playwright opens the page in a real browser engine, runs the JavaScript, and reads the rendered text by CSS selector Works on almost any page. Heavier, slower, and needs a machine that can run a browser

Playwright is free and open source, and its CI docs give the setup step for GitHub Actions: npx playwright install --with-deps. Make and n8n Cloud cannot run a browser for you. Self-hosted n8n can, through the Execute Command node (self-hosted only, and disabled by default since n8n 2.0), but at that point a script is simpler.

Bot protection

Some pricing pages refuse automated requests outright. On November 9, 2026, Make’s own pricing page answered a plain command-line request with a 403 and a challenge page titled “Just a moment…”. That page loads normally in a browser.

When a site challenges your watcher, respect it. Mark the page “check by hand”, put it on a monthly calendar reminder, and watch the vendor’s changelog or blog as well. Trying to defeat the challenge goes against what the site has asked for.

A/B tests and regional pricing

Two kinds of noise cause most false alerts.

  • A/B tests. A vendor may show different prices or plan names to different visitors while it tests them. One run sees variant A, the next sees variant B, and you get an alert for a change that never happened for most readers.
  • Regional pricing. Pages often pick a currency or price from the visitor’s location. Your scheduler runs in a data center that may sit in another country from you and your readers, so it can see a different currency.

Three habits keep the noise down:

  1. Confirm before alerting. Alert only when the new prices show up on two runs in a row, or refetch once after ten minutes.
  2. Store the currency with the price. A jump from $9 to €9 is a region switch. A jump from $9 to $11 is a price change.
  3. Pin the region when the vendor offers a currency or country setting in the URL, and use that URL in your list.

Where to keep snapshots

Tool Store Limits to know (checked November 9, 2026)
Make Data store Free plan: 1 data store with 1 MB of storage; paid plans get 10 MB per 10,000 credits (pricing page)
n8n Data table Included on Cloud Starter and Pro (pricing page); 200 MiB total by default (docs)
n8n Workflow static data Labelled experimental; saved only in production runs, not tests; JavaScript Code node only (docs)
Script A JSON file committed to Git Free, and Git history becomes a dated record of every price change

The price sections of ten pages come to a few kilobytes of text, well inside any of these. In n8n, prefer data tables; the static-data docs themselves point to them as the alternative.

Version A: Make

The scenario:

  1. Schedule: weekly.
  2. Data store: Search records. Your page list, stored together with each page’s last snapshot.
  3. HTTP: Make a request. GET the page, with Return error if HTTP request fails set to No so a blocked page becomes data.
  4. Text parser: HTML to text.
  5. Text parser: Match pattern. A regular expression that captures the text between your start and end phrases.
  6. Filter (free): continue only when the captured section differs from the stored snapshot, or is empty.
  7. Data store: Add/replace a record. Saves the new snapshot for changed pages only.
  8. Tools: Text aggregator, then Email: one message with the page name, old section, new section, and URL.

Credit math for 10 pages (Make help center and pricing page, checked November 9, 2026). Search modules cost 1 credit per run; action modules cost 1 credit per bundle; filters are free.

Module Credits per run
Search records 1
Make a request × 10 10
HTML to text × 10 10
Match pattern × 10 10
Add/replace record (changed pages only) 0 to 2 in a normal week
Text aggregator + Email 2
Total about 33 to 35

Weekly, that is about 145 to 150 credits a month, around 15% of the Free plan’s 1,000 credits. You could run the link check from our affiliate link checker guide on the same Free plan and still have most of your credits left.

Make cannot render JavaScript, so this version suits pages whose prices sit in the HTML. For the rest, use an official data feed if one exists, or a manual check.

Version B: n8n

The whole weekly run is one execution, about 4 a month.

  1. Schedule Trigger: weekly.
  2. Data table: get rows. Page list plus last snapshot.
  3. HTTP Request. Response Format Text; in Options turn on Never Error and Include Response Headers and Status; under Batching use 1 item per batch with a 2,000 ms interval.
  4. HTML node, Extract HTML Content. Read the text of your CSS selector for the price section.
  5. Code node (JavaScript). Collapse whitespace and pull out the currency amounts with a regular expression.
  6. IF: prices differ from the snapshot, section empty, or status not 200.
  7. Data table: update row with the new snapshot, then Aggregate and Send Email.

Add an error workflow with the Error Trigger node so a failed run reaches you. On n8n Cloud, Starter is €20/month billed annually for 2,500 executions (pricing page, checked November 9, 2026); worth it if you already use n8n for heavier work, wasteful for four runs a month. On a self-hosted Community edition, the run is free. For a fuller comparison of the two platforms, see Make vs n8n for solo builders.

Version C: a script on GitHub Actions

A script handles both plain and JavaScript pages and keeps snapshots in Git, which gives you a dated history of every price change for free. Whether this route suits you in general is covered in code it yourself vs Make and n8n.

GitHub Actions is free with standard runners in public repositories; on GitHub Free, private repositories get $12 a month of included usage, with Linux billed at $0.006 a minute (GitHub billing docs, checked November 9, 2026). One more reason to commit snapshots back to the repo: GitHub disables scheduled workflows in a public repository after 60 days without activity, and a weekly commit counts as activity.

The plain-fetch script (Node 20+, no dependencies). We ran it twice against n8n’s pricing page on November 9, 2026: the first run stored the snapshot, the second reported no change.

// watch-prices.mjs: weekly pricing-page check. Node 20+, no dependencies.
import { readFile, writeFile } from 'node:fs/promises';

const UA = 'MySitePriceWatch/1.0 (+https://example.com/contact)';
const PRICE = /[$€£]\s?\d[\d,.]*|\d[\d,.]*\s?[$€£]/g;

const pages = JSON.parse(await readFile('pages.json', 'utf8'));
const old = JSON.parse(await readFile('snapshots.json', 'utf8').catch(() => '{}'));
const now = {};
const alerts = [];

for (const p of pages) {
  const res = await fetch(p.url, { headers: { 'user-agent': UA }, signal: AbortSignal.timeout(20000) });
  const text = (await res.text())
    .replace(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>/gi, ' ')
    .replace(/<[^>]+>/g, ' ')
    .replace(/&nbsp;|&#160;/g, ' ')
    .replace(/\s+/g, ' ');
  const start = text.indexOf(p.start);
  const end = text.indexOf(p.end, start + p.start.length);
  if (res.status !== 200 || start < 0 || end < 0) {
    alerts.push(`- ${p.name}: could not read the price section (status ${res.status}). Check by hand.`);
    if (old[p.name]) now[p.name] = old[p.name]; // keep last good snapshot
    continue;
  }
  const section = text.slice(start, end).trim();
  const prices = section.match(PRICE) ?? [];
  now[p.name] = { prices, section, checked: new Date().toISOString().slice(0, 10) };
  const before = old[p.name];
  if (!before) continue; // first run: just store
  if (before.prices.join('|') !== prices.join('|')) {
    alerts.push(`- ${p.name}: PRICES CHANGED\n  - was: ${before.prices.join(', ')}\n  - now: ${prices.join(', ')}\n  - ${p.url}`);
  } else if (before.section !== section) {
    alerts.push(`- ${p.name}: wording changed, prices the same. ${p.url}`);
  }
}

await writeFile('snapshots.json', JSON.stringify(now, null, 2) + '\n');
await writeFile('report.md', alerts.length ? `# Pricing watch\n\n${alerts.join('\n')}\n` : '');
console.log(alerts.join('\n') || `No changes on ${pages.length} pages.`);

pages.json holds entries like { "name": "n8n", "url": "https://n8n.io/pricing/", "start": "Starter", "end": "Enterprise" }.

For a JavaScript-rendered page, swap the fetch for Playwright and read the section by selector. Add playwright to the repo and the install step to the workflow:

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ userAgent: UA });
await page.goto(p.url, { waitUntil: 'networkidle' });
const section = (await page.locator(p.selector).innerText()).replace(/\s+/g, ' ').trim();
await browser.close();

The schedule (.github/workflows/price-watch.yml). It commits the new snapshots and opens an issue when something changed:

name: Weekly price watch
on:
  schedule:
    - cron: '0 7 * * 1'   # 07:00 UTC every Monday
  workflow_dispatch:
permissions:
  contents: write
  issues: write
jobs:
  watch:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
      - run: node watch-prices.mjs
      - name: Commit snapshots
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add snapshots.json
          git commit -m "price snapshots $(date +%F)" || echo "nothing to commit"
          git push
      - name: Open an issue if anything changed
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          if [ -s report.md ]; then
            gh issue create --title "Price watch $(date +%F)" --body-file report.md
          fi

The prompt to give a coding agent

Fill in the brackets and paste it into the coding agent you use. Review the code it writes before you schedule it.

Build a weekly pricing-page watcher for my affiliate site.

Input: pages.json, an array of { name, url, mode, start, end, selector }.
  mode "fetch": plain GET, strip HTML, take the text between the phrases start and end.
  mode "browser": open with Playwright (Chromium), wait for network idle,
  read innerText of selector.
Pages to watch: [list name + URL + how to find the price section].

For each page:
- One request only. User agent "<MySite>PriceWatch/1.0 (+<contact URL>)". 20 s timeout.
- Wait 2 seconds between pages.
- Normalize the section: collapse whitespace. Extract currency amounts with their
  currency symbol ($, €, £) in page order.
- Compare with snapshots.json from the previous run.

Alert rules:
- Prices differ: HIGH. Show old and new amounts and the URL.
- Same prices, different wording: LOW. Show a short line-by-line diff.
- Status not 200, or the section is empty or missing: "check by hand".
  Keep the previous snapshot in that case.
- Only raise HIGH when the new prices appear on two consecutive runs
  (store a "pending" state between runs) to filter out A/B tests.
- A change of currency symbol alone is a region switch, not a price change.

Output: write report.md only when there is something to report; otherwise leave it empty.
Never disguise the user agent as a browser and never try to get past a bot challenge.
Then write a GitHub Actions workflow: Mondays 07:00 UTC plus a manual trigger, install
Playwright browsers only if any page uses mode "browser", commit snapshots.json, and open
a GitHub issue with report.md when it is not empty. Permissions: contents write, issues write.

Politeness and terms of service

A weekly fetch of a public page puts about as much load on the vendor’s server as one visitor does. Two checks still belong in your setup.

  • Read the vendor’s terms. Some terms of service forbid automated access to their site. If they do, drop the page from the watcher and check it by hand.
  • Read robots.txt. It is the convention sites use to tell automated clients which paths to avoid. If the pricing path is disallowed, leave it out.

Identify the watcher with an honest user agent and a contact URL, keep to one request per page per run, and stop if a vendor asks.

Which version to build

Your situation Build it in
Under about 10 pages, prices in the HTML, already on Make Make Free (about 150 credits a month)
Already running n8n n8n (about 4 executions a month)
Some pages load prices with JavaScript Script with Playwright on GitHub Actions
You want a dated history of every price Script, snapshots in Git
A vendor publishes prices as data Use that feed in any of the three
A page blocks automated requests Manual check, monthly

Who this is for, and who can skip it

Build it if your articles quote prices, plan limits, or free-tier allowances for more than a few tools. Those are the facts that go stale fastest.

Skip it for now if you cover three or four tools. Open their pricing pages on the first Monday of each month and compare them with your articles by hand.

Quick answers

How often should I check vendor pricing pages? Weekly fits most affiliate sites. Price changes are rare, and a weekly check means a wrong price stays live for at most seven days.

Why does my watcher find no prices on a page I can see in my browser? The prices are probably filled in by JavaScript after the page loads, or the site blocked the request. Check the status code first; a 200 with an empty section points to JavaScript, which a headless browser such as Playwright can handle.

Am I allowed to monitor a competitor’s pricing page automatically? Reading a public page is what every visitor does, but each site’s terms of service set the rules for automated access. Read them, follow robots.txt, keep requests to one a week, and switch to a manual check wherever automation isn’t allowed.


This build belongs to the Automation Workflows pillar. Pair it with the monthly affiliate link checker, and see Make vs n8n for solo builders to choose where to run both.