Skip to main content

Content Change Monitoring

Monitor pages for content changes and get notified when something changes — no CSS selectors or descriptions needed. extract_content() reads a page’s content and breaks it into sections. Schedule it on any number of URLs and Meter will notify you via webhook whenever content changes, telling you exactly which sections were added, modified, or removed.

Step 1: Create a content strategy

from meter_sdk import MeterClient
import os

client = MeterClient(api_key=os.getenv("METER_API_KEY"))

strategy = client.extract_content(
    url="https://docs.example.com/api/overview",
    name="Example API Docs"
)

strategy_id = strategy["strategy_id"]
print(f"Strategy: {strategy_id}")
print(f"Sections found: {len(strategy['preview_data'])}")

for section in strategy["preview_data"]:
    print(f"  [{section['level']}] {section['heading']}")
extract_content() is instant — no LLM call, no waiting. It returns in ~2 seconds.

Step 2: Schedule monitoring with a webhook

One strategy can monitor many URLs. Create a schedule with all the URLs you want to track:
schedule = client.create_schedule(
    strategy_id=strategy_id,
    urls=[
        "https://docs.example.com/api/overview",
        "https://docs.example.com/api/authentication",
        "https://docs.example.com/api/endpoints",
        "https://docs.example.com/api/errors",
    ],
    interval_seconds=3600,  # check hourly
    webhook_url="https://your-app.com/webhooks/content-changes",
)

print(f"Schedule: {schedule['schedule_id']}")
print(f"Next run: {schedule['next_run_at']}")

# Store the webhook secret (shown only once)
if schedule.get("webhook_secret"):
    print(f"Webhook secret: {schedule['webhook_secret']}")

Step 3: Monitor many URLs at scale

For monitoring hundreds of URLs, create the strategy once and attach all URLs to the schedule:
# Your list of URLs to monitor
urls = [
    "https://docs.example.com/api/overview",
    "https://docs.example.com/api/authentication",
    "https://docs.example.com/guides/quickstart",
    # ... hundreds more
]

# One strategy, one schedule, many URLs
strategy = client.extract_content(
    url=urls[0],  # any representative URL
    name="Example Docs Monitor"
)

schedule = client.create_schedule(
    strategy_id=strategy["strategy_id"],
    urls=urls,
    cron_expression="0 */6 * * *",  # every 6 hours
    webhook_url="https://your-app.com/webhooks/content-changes",
    webhook_metadata={"source": "example-docs", "team": "content"}
)
Use webhook_metadata to tag schedules. The metadata is included in every webhook payload, making it easy to route changes in your handler.

Step 4: Handle webhook notifications

When content changes, Meter sends a webhook with the changed sections. Here’s a FastAPI handler:
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib

app = FastAPI()
WEBHOOK_SECRET = "your-webhook-secret"

@app.post("/webhooks/content-changes")
async def handle_content_change(request: Request):
    # Verify signature
    body = await request.body()
    signature = request.headers.get("X-Webhook-Secret")
    expected = hmac.new(
        WEBHOOK_SECRET.encode(), body, hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature or "", expected):
        raise HTTPException(status_code=401)

    payload = await request.json()
    url = payload["url"]
    metadata = payload.get("metadata", {})
    new_items = payload.get("new_items", [])
    removed_items = payload.get("removed_items", [])

    print(f"Content changed on {url}")
    print(f"  {len(new_items)} sections added/modified")
    print(f"  {len(removed_items)} sections removed")

    for section in new_items:
        print(f"  Changed: {section['heading']}")
        # Trigger your downstream action:
        # - Re-scrape with Firecrawl
        # - Update RAG embeddings
        # - Notify your team

    return {"status": "ok"}

Step 5: Pull-based alternative

If you prefer polling over webhooks, use get_schedule_changes():
changes = client.get_schedule_changes(
    schedule_id=schedule["schedule_id"],
    mark_seen=True
)

if changes["count"] > 0:
    for change in changes["changes"]:
        url = change["url"]
        sections = change["results"]
        print(f"{url}: {len(sections)} sections extracted")
Or view the full change history:
changelog = client.get_schedule_changelog(
    schedule_id=schedule["schedule_id"],
    limit=10
)

for entry in changelog["entries"]:
    print(f"{entry['completed_at']}{entry['url']}")
    print(f"  +{entry['new_items_count']} / -{entry['removed_items_count']}")

Complete example

End-to-end script that sets up monitoring for a list of URLs:
"""content_monitor.py — Monitor pages for content changes."""

from meter_sdk import MeterClient
import os
import json

client = MeterClient(api_key=os.getenv("METER_API_KEY"))

# Configuration
URLS = [
    "https://docs.example.com/api/overview",
    "https://docs.example.com/api/authentication",
    "https://docs.example.com/api/endpoints",
    "https://docs.example.com/guides/quickstart",
    "https://docs.example.com/guides/deployment",
]
WEBHOOK_URL = "https://your-app.com/webhooks/content-changes"
STATE_FILE = "monitor_state.json"


def load_state():
    try:
        with open(STATE_FILE) as f:
            return json.load(f)
    except FileNotFoundError:
        return {}


def save_state(state):
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, indent=2)


def setup():
    """One-time setup: create strategy + schedule."""
    state = load_state()

    if "strategy_id" not in state:
        strategy = client.extract_content(
            url=URLS[0],
            name="Docs Content Monitor"
        )
        state["strategy_id"] = strategy["strategy_id"]
        print(f"Created strategy: {state['strategy_id']}")
        print(f"Preview sections: {len(strategy['preview_data'])}")

    if "schedule_id" not in state:
        schedule = client.create_schedule(
            strategy_id=state["strategy_id"],
            urls=URLS,
            interval_seconds=3600,
            webhook_url=WEBHOOK_URL,
            webhook_metadata={"monitor": "docs-content"},
        )
        state["schedule_id"] = schedule["schedule_id"]
        state["webhook_secret"] = schedule.get("webhook_secret")
        print(f"Created schedule: {state['schedule_id']}")
        print(f"Webhook secret: {state['webhook_secret']}")

    save_state(state)
    print(f"\nMonitoring {len(URLS)} URLs. Webhook → {WEBHOOK_URL}")
    return state


if __name__ == "__main__":
    setup()
Run once to set up:
export METER_API_KEY="sk_live_..."
python content_monitor.py
That’s it — Meter handles the recurring checks, diffing, and webhook delivery from here.

See also

Change Detection

How Meter detects and reports changes

Webhooks Guide

Webhook payload formats and verification

Schedule Methods

Full schedule API reference

Strategy Groups

Organize monitors with groups

Need help?

Email me at mckinnon@meter.sh