ScreenshotNeo

BlogHow-to

How to Monitor Terms of Service Changes

Track a provider’s terms page on a schedule, keep dated versions, and review alerts against the current source. Here’s a practical workflow and runnable script.

By the ScreenshotNeo team4 October 202611 min read

To monitor a provider’s Terms of Service (ToS), identify its canonical terms page, check the relevant text on a schedule, and keep dated copies so you can compare revisions. A change monitor can alert you after a check detects a difference; it cannot guarantee that every revision was caught, establish legal significance, or count as legal notice.

This guide shows a self-hosted Python monitor, ways to schedule and maintain it, and how to choose a hosted service. Use a provider’s own terms page as the source of truth and review material changes yourself.

1. Choose the page and content to monitor

  1. Find the canonical URL. Prefer the terms page linked from the provider’s own site. Record the URL and the date monitoring begins. If the provider publishes separate terms for products, regions, or customer types, monitor the version that applies to you.
  2. Choose a stable region. If the page has a clear main content element, monitor that text rather than a navigation bar, footer, or rotating announcement. A selected region can reduce irrelevant changes. If the page is difficult to isolate or changes its markup often, monitor the full page and expect to review more alerts. Visualping documents both whole-page and selected-area monitoring and visual, text, and code change detection in its [help documentation](https://help.visualping.io/en/articles/4438913).
  3. Decide what counts as a change. Text changes are usually the most useful starting point for terms. A visual comparison can help when formatting or layout conveys context, but page styling, timestamps, or cookie notices can produce noise.
  4. Set an interval based on your needs. Checks only detect a change when a subsequent check runs and compares the page with a previous version. Choose a frequency that fits your desired detection delay and the service’s limits. An alert cannot arrive before the monitor’s next successful check. Visualping describes alerts as following a check and comparison in its [alert guidance](https://help.visualping.io/en/articles/10154433).
  5. Keep a history. Preserve dated copies or diffs, not just the latest page. Distill documents saved change history and comparisons between historical versions in its [change-history guide](https://distill.io/docs/web-monitor/change-history-and-highlighted-changes/).

2. Run a simple self-hosted Python monitor

This script fetches a public terms page, extracts its main text, stores each observed version, and prints a unified diff when the text changes. It is intended to run once per scheduled invocation. It does not send notifications or determine whether a change is legally important.

Install and configure

Use Python 3.10 or newer. Create a directory for the script and its history, then install the two dependencies:

python -m venv .venv
. .venv/bin/activate
python -m pip install requests beautifulsoup4

On Windows PowerShell, activate with .venv\Scripts\Activate.ps1. Save the following as monitor_terms.py. Set TERMS_URL to the provider’s canonical URL. If the page has a stable main-content selector, set TERMS_SELECTOR too; otherwise leave it unset to extract the page body.

import difflib
import hashlib
import os
import re
from datetime import datetime, timezone
from pathlib import Path

import requests
from bs4 import BeautifulSoup

URL = os.environ.get("TERMS_URL", "https://example.com/terms")
SELECTOR = os.environ.get("TERMS_SELECTOR", "").strip()
HISTORY = Path(os.environ.get("TERMS_HISTORY", "terms-history"))
TIMEOUT_SECONDS = 30


def normalize(text: str) -> str:
    """Normalize whitespace while retaining paragraph boundaries."""
    lines = [re.sub(r"\s+", " ", line).strip() for line in text.splitlines()]
    return "\n".join(line for line in lines if line)


def fetch_terms() -> str:
    response = requests.get(
        URL,
        headers={"User-Agent": "TermsChangeMonitor/1.0 (personal monitoring)"},
        timeout=TIMEOUT_SECONDS,
    )
    response.raise_for_status()
    content_type = response.headers.get("Content-Type", "")
    if "html" not in content_type.lower():
        raise ValueError(f"Expected an HTML page, got Content-Type: {content_type!r}")

    soup = BeautifulSoup(response.text, "html.parser")
    for node in soup(["script", "style", "noscript", "svg"]):
        node.decompose()

    selected = soup.select_one(SELECTOR) if SELECTOR else None
    if SELECTOR and selected is None:
        raise ValueError(f"Selector did not match any element: {SELECTOR!r}")
    root = selected or soup.body or soup
    text = normalize(root.get_text("\n"))
    if len(text) < 100:
        raise ValueError(
            f"Extracted only {len(text)} characters; page may be blocked, empty, or JavaScript-rendered"
        )
    return text


def main() -> None:
    HISTORY.mkdir(parents=True, exist_ok=True)
    current = fetch_terms()
    digest = hashlib.sha256(current.encode("utf-8")).hexdigest()
    versions = sorted(HISTORY.glob("*.txt"))
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")

    if versions:
        previous_path = versions[-1]
        previous = previous_path.read_text(encoding="utf-8")
        previous_digest = hashlib.sha256(previous.encode("utf-8")).hexdigest()
        if digest == previous_digest:
            print(f"No text change detected. Latest saved version: {previous_path}")
            return

        new_path = HISTORY / f"{timestamp}-{digest[:12]}.txt"
        new_path.write_text(current + "\n", encoding="utf-8")
        diff = difflib.unified_diff(
            previous.splitlines(),
            current.splitlines(),
            fromfile=str(previous_path),
            tofile=str(new_path),
            lineterm="",
        )
        print("Change detected; additions begin with + and removals with -:")
        print("\n".join(diff))
        return

    first_path = HISTORY / f"{timestamp}-{digest[:12]}.txt"
    first_path.write_text(current + "\n", encoding="utf-8")
    print(f"Saved initial baseline: {first_path}")


if __name__ == "__main__":
    try:
        main()
    except (requests.RequestException, ValueError) as error:
        raise SystemExit(f"Monitor failed: {error}")

Run it once to save the baseline, and again after the terms page may have changed:

export TERMS_URL='https://provider.example/legal/terms'
export TERMS_SELECTOR='main'
python monitor_terms.py

Remove the TERMS_SELECTOR line if main is not a stable selector on that site. Keep the history directory somewhere persistent and backed up. The script does not overwrite earlier snapshots: each detected change gets a timestamped file. The first run only creates a baseline, so it cannot report changes that occurred before monitoring began.

Schedule repeated checks

On a Unix-like system, cron can run the script every six hours, for example. Use absolute paths for the project and Python executable, and redirect output to a log you actually review:

0 */6 * * * cd /opt/terms-monitor && /opt/terms-monitor/.venv/bin/python monitor_terms.py >> monitor.log 2>&1

That schedule means a change may wait until the next run to be noticed, and failed runs extend the delay. Configure your scheduler’s failure notification or send the output to an alerting mechanism if unattended failures matter. If the job runs on a laptop, it will not run while the device is off or asleep. For always-on monitoring, run it on a server or choose a cloud monitor.

3. Use a hosted monitor or a local monitor

A hosted monitor checks from the vendor’s infrastructure, so your computer can be off. A local monitor runs in your browser or on your device and depends on that environment being available. Distill describes both local and cloud modes; its documentation also notes that a site blocking remote browsers may require proxy configuration or local monitoring ([Distill overview](https://distill.io/docs/web-monitor/what-is-distill/)).

When comparing options, check these points on the vendors’ current pages:

  • Selection: Can you monitor just the terms text, or must you compare the entire page?
  • Execution: Does it run in the cloud or only while your browser or device is available?
  • History: Does it retain prior versions and show a useful before-and-after diff?
  • Alerts: Which notification channels are supported, and can you reduce noise?
  • Access: Can it load pages that require JavaScript or block remote browsers?
  • Limits and price: Check monitor counts, check frequency, notification quotas, and current plan terms directly. These can change.

Visualping’s help pages describe selected-area and full-page checks, change detection, comparison, and cloud monitoring ([overview](https://help.visualping.io/en/articles/4438913)). Distill’s documentation describes local and cloud monitoring, history, and access workarounds ([overview](https://distill.io/docs/web-monitor/what-is-distill/), [history](https://distill.io/docs/web-monitor/change-history-and-highlighted-changes/)). These are vendor descriptions, not independent proof of completeness or performance. Distill’s [official page](https://distill.io/) lists plan limits; verify current limits before relying on them.

Terms of Service; Didn’t Read is a separate terms-reading aid described as a browser extension with summaries or ratings for supported services. The available research does not establish comprehensive, real-time change alerts, so do not treat it as a change monitor without checking current primary documentation.

4. Review an alert and preserve useful evidence

  1. Confirm the monitor ran successfully and record the check time, monitored URL, and version it compared.
  2. Open the provider’s current canonical terms page directly. Do not rely only on an alert preview or cached copy.
  3. Compare the old and new text. Read surrounding sections, since a changed definition or cross-reference can alter the meaning of a paragraph.
  4. Save the dated old and new text, the diff, and the source URL. Keep enough context to identify which provider page and version you reviewed.
  5. Decide whether the change matters to your use of the service. A diff identifies webpage changes; it does not interpret a contract. Seek qualified advice if the consequences need legal interpretation.

Scheduled checks can miss a change if the page changes and reverts between checks, if a request fails, if content is rendered differently for the monitor, or if the extraction rule stops matching. Treat an alert as a prompt to inspect, and an absence of alerts as no proof that nothing changed.

5. Troubleshooting

Symptom Likely cause What to do
The script returns HTTP 403 or 429 The site denies automated requests or rate-limits repeated checks. Reduce frequency, check the provider’s access rules, and try a local monitor or a vendor-supported access method. Do not attempt to bypass an access control.
It reports a change every run The page contains rotating content, timestamps, or unstable whitespace; the selector includes unrelated content. Choose a more focused stable selector, normalize irrelevant formatting carefully, or configure the monitor to ignore known dynamic regions.
It reports no change, but the page looks different The visible terms may load through JavaScript, the selector may miss the changed section, or the monitor may have fetched a different variant. Inspect the stored text, confirm the selector matches the relevant content, and check whether the site requires browser rendering. Compare against the live source page.
The extracted text is empty or unusually short The site may have returned a bot challenge, an error page, or a JavaScript shell. Check the response status and page manually. Use a monitor capable of loading the page in a browser environment, or monitor the provider’s alternate canonical terms page if one exists.
The monitor stopped alerting The scheduled job may have stopped, credentials or network access may have changed, or the alert destination may be broken. Inspect check history and job logs, run a manual check, and verify notification delivery. Visualping’s [alert guide](https://help.visualping.io/en/articles/10154433) recommends confirming that a new check occurred.
The page changed but no alert arrived The revision happened after the last check, a check failed, or the change did not meet the configured detection rule. Check execution history, run a fresh comparison, and adjust interval or detection mode. No schedule can detect a change before a successful comparison.
Python says the selector matched nothing The configured CSS selector does not exist in the fetched HTML, or the page is rendered client-side. Inspect the fetched HTML and update TERMS_SELECTOR. If content is inserted after load, use a browser-based monitor or a stable server-rendered source.
Two versions differ only in formatting Markup, whitespace, or line wrapping changed without a meaningful text revision. Compare normalized text and inspect the live page. Keep the original snapshots so normalization does not erase evidence.

6. Reliability, performance, and cost

  • Detection delay: The interval sets a lower bound on how quickly a scheduled monitor can notice a revision; failed checks and notification delays add more time. Choose the interval based on the consequence of learning late.
  • Reliability: Monitor the monitor. Review successful check history, retain logs, verify notification delivery, and periodically confirm the monitored URL and selector still refer to the terms.
  • Performance: A text-only HTTP fetch is typically lighter than browser rendering, but it may not see content loaded by client-side JavaScript. Browser-based checks can handle rendered pages but use more resources and may be slower. The research does not establish comparative benchmarks.
  • Storage: Save only versions and diffs you need, organize them by provider, and back up the history directory. A plain text archive is easy to diff and search.
  • Cost: A self-hosted script uses your compute, storage, and notification infrastructure; a hosted service may charge according to current monitor, frequency, or alert limits. Verify vendor pricing and quotas before depending on a plan.
  • Scope: Monitor the exact terms relevant to your account and region. If the provider has separate policies or product-specific terms, one URL may not cover them all.

Or skip the browser setup

If you also need a visual record of what the terms page looked like, [ScreenshotNeo](https://screenshotneo.com) can capture the page with one API request. A screenshot is useful as visual evidence alongside a text monitor; it does not by itself detect revisions or send change alerts. See the [ScreenshotNeo API docs](https://screenshotneo.com/docs/) for request options.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Replace the example URL with the provider’s terms URL. ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Every feature is on every plan.

Sign up for 1,000 free screenshots a month, with no card required.

FAQ

No. This workflow records webpage observations. The research available for this guide does not establish notice requirements or the legal effect of a specific update.

Should I monitor a privacy policy too?

If you need to track it, add its canonical URL as a separate monitor and keep a separate history. Do not assume that monitoring the terms page also covers other policies.

Can I tell whether a change is important from the diff alone?

No. A diff shows text or page changes. Read the surrounding terms and seek qualified advice when you need a legal interpretation.

Will a monitor catch every revision?

No. A change can occur between checks, access can fail, and the page may render differently for the monitor. Check history helps you spot monitoring gaps but cannot prove complete detection.