Website Change Monitor (MCP) avatar

Website Change Monitor (MCP)

Pricing

Pay per event + usage

Go to Apify Store
Website Change Monitor (MCP)

Website Change Monitor (MCP)

Website change monitor and webhook scheduler for AI agents. Track job completion, price drops, restocks, uptime, JSON values, and page changes after the agent disconnects, then POST the result to Slack, Discord, n8n, Zapier, Make, or your own webhook.

Pricing

Pay per event + usage

Rating

0.0

(0)

Developer

JONGMIN LEE

JONGMIN LEE

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

a day ago

Last modified

Share

Website change monitor and webhook scheduler for AI agents

Website Change Monitor is a persistent website monitoring API and MCP server for AI agents and automations. Track a job until it completes, watch a price until it drops, detect a restock, wait for a service to recover, or monitor visible page text — then receive a webhook when the condition becomes true.

Tell us what you are waiting for. We keep checking while you are gone, and when it happens we POST it to a URL you choose.

An agent cannot wait. Its session ends and it is gone — it cannot watch a page, poll a job, or wake up later. This can, and it wakes the agent back up when the moment arrives.

Quick start

Try it in Apify Console

  1. Click Try for free.
  2. Select Register a website watch.
  3. Enter a public HTTPS URL, the check interval, and the condition you are waiting for.
  4. Optionally add an encrypted callback URL for Slack, Discord, n8n, Zapier, Make, or your own service.
  5. Start the Actor. The Dataset returns the watchId, next check time, remaining checks, and evidence.

Use Get a watch's status or Cancel a watch with the returned watchId. Selecting Check my existing watches now performs one immediate round for your account.

Connect an MCP client

Streamable HTTP endpoint:

https://blyck--website-change-monitor.apify.actor/mcp

Send your Apify API token as a bearer token. The token identifies your account so watches remain private to you.

{
"mcpServers": {
"website-change-monitor": {
"command": "npx",
"args": [
"mcp-remote",
"https://blyck--website-change-monitor.apify.actor/mcp",
"--header",
"Authorization: Bearer <YOUR_APIFY_TOKEN>"
]
}
}
}

Or call the MCP tool directly:

curl -X POST "https://blyck--website-change-monitor.apify.actor/mcp" \
-H "Authorization: Bearer <YOUR_APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "watch.url-change",
"arguments": {
"url": "https://api.example.com/jobs/8471",
"interval": "5m",
"when": "json",
"path": "$.status",
"op": "eq",
"value": "completed",
"callbackUrl": "https://hooks.example.com/apify",
"checkBlocks": 1,
"label": "render job 8471"
}
}
}'

What you can wait for

You wantHow you say it
A long job to finishwhen: json, path: $.status, value: completed
A price to dropwhen: json, path: data.price, value: 30000, op: lt
A sold-out item to come backwhen: contains, value: back in stock
A service to recoverwhen: status, value: 200
A page to change at allwhen: changed (the default)

A condition fires once, on the moment it becomes true — not on every check. By default the watch then ends, because waiting is over. Pass stopWhenMet: false to keep monitoring instead.

Why a condition instead of "tell me if it changed"

Real pages change on every single load: CSRF tokens, session ids, rotating banners, inline timestamps. "Tell me when it changes" turns into noise, and noise is worse than nothing.

Saying what you are actually waiting for removes that by construction. And when you do use when: changed, we compare the visible text — scripts, styles, comments and markup are stripped first — so a rotating tracking id does not wake you at 3am.

Two ways to use it

1. As an MCP server (AI agents)

Standard JSON-RPC 2.0: initialize, tools/list, tools/call. There is no session handshake — every request is independent, so restarts and scaling never break your integration.

ToolWhat it does
watch.url-changeRegister a URL, a condition, and where to notify you. Register and disconnect.
watch.statusWhat you are waiting for, whether it has happened, remaining checks, and evidence.
watch.cancelStop waiting.
schedule.atFire a notification at a future point in time.

2. As a normal Actor

The generated Input UI can register, inspect, cancel, and immediately check watches without an MCP client. You do not have to create your own recurring Apify Schedule — the service already performs the checks after registration.

Getting notified

Pass a callbackUrl when you register. When the watch fires, we POST JSON to it:

{
"kind": "condition_met",
"at": "2026-07-26T16:19:00.130Z",
"watchId": "watch-87a670fd",
"url": "https://api.example.com/jobs/8471",
"when": "json",
"path": "$.status",
"expected": "completed",
"actual": "completed",
"label": "render job"
}

Anything that accepts an HTTPS POST works — a Slack or Discord incoming webhook, an n8n / Zapier / Make hook, or your own endpoint.

Waking an agent

An agent that has ended cannot be notified over MCP: the connection is gone with the session. A callback is the way back in.

If your agent is itself an Apify Actor, point the callback at its run endpoint and the event starts it:

callbackUrl: https://api.apify.com/v2/acts/<your-actor>/runs?token=<token>

Use a token scoped to that one Actor. The callback URL is stored so we can reach you later, so treat it like any other credential you hand to a third party — and prefer an automation hook (n8n, Zapier, Make) if you would rather not share a platform token at all.

If your agent runs in a desktop client (Claude Desktop, Cursor, ChatGPT), it has no inbound address. Send the callback to Slack or Discord instead, and pick the work back up from there.

What you get back

Every result carries evidence, and a result that cannot be evidenced is reported as a failure rather than dressed up as success.

{
"status": "ok",
"data": { "watchId": "watch-a4f93d0a", "nextCheckAt": "…", "checksRemaining": 1000 },
"evidence": { "reused": false, "when": "json", "stopWhenMet": true, "intervalMs": 300000 },
"latencyMs": 10
}

Guarantees enforced by the runtime, not by prompt:

  • No unevidenced success. A handler returning ok without evidence is downgraded to failed.
  • No undeclared inputs. Fields outside the declared schema are rejected (unknown_field), which closes the instruction-injection path through arguments.
  • No silently useless watch. A condition missing the value or path it needs is rejected at registration instead of never firing.
  • Failures stay failures. A check that could not be fetched is reported as failed and is not billed.
  • Idempotent registration. Re-registering the same target, interval and condition returns the existing watch instead of duplicating it.

Billing

Checks are prepaid in blocks when you register a watch. One block buys one wait, up to 1,000 checks.

If your condition is met after 12 checks, the watch is done and the remaining checks are not refunded or carried over — you paid for the answer, not for the polling. If you want a watch that keeps running past the first hit, register it with stopWhenMet: false and the block is spent the usual way.

watch.url-change { url, when, value, checkBlocks: 3 }3,000 checks prepaid
EventWhenCharged
watch-registerA watch is registered$0.25 per 1,000-check block
schedule-registerA one-off schedule.at is registered$0.002
status-queryA status or cancel call succeeds$0.002
watch-triggerA condition was met, a change was detected, or a schedule fired$0.01

Typical 30-day watch costs before trigger or optional status-query events:

Check intervalChecks in 30 daysBlocksRegistration price
1 hourabout 7201$0.25
15 minutesabout 2,8803$0.75
5 minutesabout 8,6409$2.25

At a 15-minute interval, one block lasts about 10 days; three blocks about a month.

What is not charged

  • Failed calls, and checks that could not fetch the target — a failed fetch does not consume your prepaid budget either
  • Re-registering an identical watch. It returns the existing watch instead of duplicating it and costs nothing, so agent retries are safe

When the budget runs out the watch moves to exhausted and stops being checked. watch.status returns checksRemaining so you can top up in time — register again to buy a new budget.

Limits

  • Delivery is best-effort, and never silently so. We retry once; a 4xx is treated as your configuration being wrong and is not retried. The outcome is stored on the trigger and returned by watch.status, so you can always tell "nothing happened" apart from "we could not reach you".
  • Public HTTPS callbacks only. Private, loopback and link-local targets are rejected, including hostnames that resolve to them. Redirects are not followed and the request times out after 5 seconds.
  • The same applies to the URL you watch. It is resolved and checked before every fetch, redirects are re-checked at each hop, and anything pointing at a private, loopback or link-local address is refused. This service is not a way to reach networks you cannot reach yourself.
  • Public HTTPS targets only. Pages behind a login, paywall, or bot protection are out of scope and are reported as failures rather than worked around.
  • when: changed tells you that the visible text changed, with a fingerprint as evidence. It does not diff the content for you. Per-element selectors are not supported yet — use a condition when you care about one specific thing.
  • A watch that fails 5 times in a row is marked failing and stops consuming checks.
  • Minimum interval is 5 minutes; maximum is 24 hours. A fetch times out after 15 seconds, and the response body is read up to 512 KB — reading stops there rather than downloading the rest.
  • Trigger history is kept to the most recent 200 notifications per account, and watches that ended (cancelled, exhausted, met) are removed 30 days later.