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 " \n Monitoring { 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