Skip to content

Page content monitoring (Content monitor)

Content monitor watches the content, not the resource availability: Notifly fetches the specified URL every N seconds, extracts the value of interest from the response and compares it with the previous one. As soon as the value changes — an alert is sent. This is an active monitor (Notifly initiates the check), but it answers the question “what changed on the page” rather than “is the site up”.

Typical use cases:

  • Price — track the price of a product on a store page or a competitor’s site.
  • Status / availability — “in stock” ↔ “out of stock”, order status, service status.
  • Competitor content — a new blog post appeared, the price list changed, the changelog updated.
  • JSON API — the value of a field in a public JSON response (rate, balance, version).

What to compare is defined by the mode (mode). Five modes are supported:

modeWhat is extractedField selector
hashSHA-256 of the entire response body — alert on any page changenot needed
jsonValue by JSONPath from a JSON responserequired — JSONPath, e.g. $.price or $.items.0.status
textFirst match of a regular expression (Go syntax) in the response textregex; if empty — behaves like hash
cssText of the first element matched by a CSS selectorrequired — valid CSS selector
xpathText of the first node matched by XPath (attributes too: //a/@href)required — valid XPath expression

The check logic is the same for all modes: extract the value → compare with the snapshot from the previous check → if different, send an alert and remember the new value. The first check just stores the current value and does not send an alert. Whitespace in element text (css/xpath) is collapsed, so line breaks in the markup don’t produce false changes.

If a value cannot be extracted (the selector found nothing, the element is empty, the JSONPath is missing, the regex didn’t match), that is not a “change” but a check error: the monitor goes down (see Notifications).

hash reacts to any changed byte — including counters, tokens and timestamps in the markup. For “live” pages it’s more reliable to watch a specific fragment with css/xpath.

Instead of a single mode+selector you can provide an array of rules (rules) — up to 20 rules per monitor. Each rule is { "mode", "selector", "label" }, where label is an optional human-readable name (it also appears in the alert text). An alert is triggered if any rule changed. This is convenient when you need to monitor several values on the same page (e.g., price + availability + rating).

"rules": [
{ "mode": "css", "selector": ".product-price", "label": "Price" },
{ "mode": "css", "selector": ".availability", "label": "Availability" },
{ "mode": "text", "selector": "Discount\\s+\\d+%", "label": "Discount" }
]

If rules is set, top-level fields mode/selector are ignored. If rules is empty — the single mode (mode+selector) works, preserved for backward compatibility.

The snapshot is stored per rule: reordering rules causes no false alerts, and a new or modified rule only records its value on the first check.

By default the page is loaded via a plain HTTP GET. If the needed value is injected by scripts in the browser (SPA, lazy-loaded price, etc.), enable the flag useBrowser: true — then the page is rendered in headless Chromium and values are extracted from the final DOM after JS execution (text — from the page’s visible text, hash — from the rendered HTML).

If a browser-mode check fails, Notifly takes a screenshot of the page (when screenshot storage is configured on the server) and attaches it to the error alert: its key arrives in the message extras under notifly::screenshot, the image is served by GET /browser-shot/<key> and a preview is shown on the message card in the admin UI. Change alerts are sent without a screenshot by default. Frames not attached to an alert are not kept; access is limited to the monitor owner; frames live within the plan’s storage quota and are deleted together with the monitor.

EventWhenText
ChangeThe value of at least one rule differs from the previous check’s snapshot — on every changealertMessage + a list of “was → now” changes (values truncated to 100 characters)
Check errorThe page failed to load (network, timeout, HTTP code ≥ 400) or a rule extracted no value; once per outageОшибка проверки: <reason> (“Check error”)
Check recoveredFirst successful check after down — only if an error alert was sent✅ Проверка восстановлена (“Check recovered”) + what the error was

All three are sent with the alertTitle title and alertPriority priority. The change text is built as Markdown, so enable alertMessageMarkdown: true (the admin UI does this automatically). The service part of the text is currently generated in Russian. Example alert:

Product #42 price changed!
🔔 Обнаружено изменение
**Price**
было: ~~1 299 ₽~~
стало: **1 199 ₽**

If a rule has no label, the text says “правило N” (“rule N”).

Monitor statuses: pending (created or resumed), up, down (the last check was an error) and paused. A content monitor has no “N failures in a row” threshold: the very first error puts it down.

  • No spam. The monitor reports an error once: the alert time is stored in alertedAt, and while the monitor is down no repeated messages are sent. Re-arm: if you change check parameters via PUT /content-monitor/:id (URL, mode, selector, rules, useBrowser, interval, timeout), the mark is cleared and a check runs right away — if the error persists, a fresh alert arrives.
  • A timeout resets the snapshot. After a timeout error the first successful check records the values anew and does not report a change. With other errors the snapshot is kept, so a change that happened during the outage will be noticed.
  • Notifly infrastructure failures (e.g. the browser service is unavailable) are not the site’s fault: no alert, the status doesn’t change, the quota is refunded, and the check is retried in 2 minutes.
  • Maintenance windows: during a window checks run and are recorded in history, but the error alert is deferred until the window ends, and detected changes are neither reported nor accepted as the new snapshot — after the window the page is compared with the pre-maintenance snapshot. A change that outlives the window will be noticed; a temporary “under maintenance” stub will not.
  • Quiet hours suppress escalation to external channels for messages with priority below breakthroughPriority (8 by default).
  • The channel’s status page: going down (a check error, not a change) opens an auto-incident, recovery closes it.
  • Each check is one event of the daily quota; when it is exhausted, checks are postponed until the next day.

Before creating a monitor, it is convenient to try and validate a rule on the fly — there are two endpoints that do not require creating a monitor. Both load the page the same way as the live monitor and apply the rule with the same code. The loaded page is cached for 90 seconds: repeated tests on the same URL don’t reload it.

Test a rule — POST /content-monitor/test-rule

Section titled “Test a rule — POST /content-monitor/test-rule”

Loads the page and applies a single rule, returning the extracted value or a clear error:

Окно терминала
curl -X POST "$NOTIFLY_URL/content-monitor/test-rule" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"url": "https://example.com/product/42",
"useBrowser": false,
"mode": "css",
"selector": ".product-price"
}'
# → {"ok":true,"value":"1 299 ₽"}

Response (always 200): ok (boolean), value (extracted value, truncated to 200 characters; for hash — a short page fingerprint), error (error text if the page failed to load or the rule didn’t work) and note (diagnostics, e.g. about browser mode falling back to GET).

Suggest a selector using AI — POST /content-monitor/suggest-selector

Section titled “Suggest a selector using AI — POST /content-monitor/suggest-selector”

Notifly loads the page, and the AI uses your description to decide what to track and to find its current value on the page; from that value the server itself builds a stable CSS/XPath selector, and if that fails, the AI synthesizes a selector from a page fragment (with verification and retries). For a JSON response the json mode is chosen. The result is always verified against the real page content, so the sampleValue in the response is the actual current value:

Окно терминала
curl -X POST "$NOTIFLY_URL/content-monitor/suggest-selector" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"url": "https://example.com/product/42",
"useBrowser": false,
"prompt": "track the product price"
}'

The response includes: mode, selector, label, sampleValue (current value on the page), reason (why), confidence (high/medium/low, when rated), alternatives (other candidates with their sampleValue), fallback (true if the selector was chosen heuristically — the h1/title heading or the page hash — without AI or after it failed) and note. The prompt field is up to 500 characters. A request like “monitor the whole page” immediately returns hash mode without calling the AI. If the page fails to load, the response is 502.

  1. Open app.notifly.ru → Monitors → Create → “Page content changed” (or the Content tab → “Add content monitor”).
  2. Enter the URL and enable “Browser mode” if needed. In the “AI selector picker” block describe in words what to watch, click “Generate selector” and “Add to rules” — or set up the rules manually.
  3. Use a rule’s “Test” button to make sure the extracted value is exactly what you need.
  4. Set the interval, timeout, channel and alert text (Markdown is supported) — and save.

The form doesn’t configure the alert title and priority (priority 5) — set them via the REST API or MCP. Note that saving the monitor from the form resets the priority to 5.

Via the REST API — POST /content-monitor

Section titled “Via the REST API — POST /content-monitor”

Single mode (monitoring one CSS value):

Окно терминала
curl -X POST "$NOTIFLY_URL/content-monitor" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"appid": 12345,
"name": "Product #42 price",
"url": "https://example.com/product/42",
"mode": "css",
"selector": ".product-price",
"useBrowser": false,
"intervalSec": 3600,
"timeoutSec": 15,
"alertMessage": "Product #42 price changed!",
"alertMessageMarkdown": true,
"alertPriority": 7
}'

Multiple mode (several rules on one page):

Окно терминала
curl -X POST "$NOTIFLY_URL/content-monitor" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"appid": 12345,
"name": "Product card",
"url": "https://example.com/product/42",
"rules": [
{ "mode": "css", "selector": ".product-price", "label": "Price" },
{ "mode": "css", "selector": ".availability", "label": "Availability" }
],
"intervalSec": 3600,
"alertMessage": "The product card content changed.",
"alertMessageMarkdown": true,
"alertPriority": 5
}'

Monitoring a field in a JSON API:

Окно терминала
curl -X POST "$NOTIFLY_URL/content-monitor" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"appid": 12345,
"name": "Rate from API",
"url": "https://api.example.com/rates",
"mode": "json",
"selector": "$.usd.value",
"intervalSec": 600,
"alertMessage": "USD rate changed.",
"alertMessageMarkdown": true
}'
FieldTypeRequiredDescription / constraints
appiduintyesID of the channel to send the alert to
namestringyesMonitor name, 1–200 characters
urlstringyesValid http(s) URL pointing to a public address
modestringyes*Single mode: hash / json / text / css / xpath
selectorstringdependsJSONPath for json, regex for text, CSS for css, XPath for xpath; required for json/css/xpath
rulesarrayyes*Array of rules {mode, selector, label}, up to 20; alternative to mode+selector
useBrowserboolnotrue — render via headless Chromium
intervalSecuintyesCheck interval, 60–86400 seconds
timeoutSecuintnoLoad timeout, default 15, maximum 60
alertMessagestringyesChange notification text, 1–2000 characters
alertTitlestringnoTitle of all the monitor’s notifications
alertMessageMarkdownboolnoRender notifications as Markdown (true recommended)
alertPriorityintnoNotification priority, 0–10

* You must set either rules (multiple mode) or mode+selector (single mode). If rules is non-empty — mode/selector are ignored. CSS selectors, XPath expressions and regexes are syntax-checked on save (400 on error).

A new monitor is created with status pending and is checked almost immediately (nextCheckAt = “now”). After changing check parameters (URL, mode, selector, rules, useBrowser, interval, timeout) the check is rescheduled for “now”; editing the name, alert title/message or priority does not shift the schedule. PUT /content-monitor/:id accepts the same full set of fields (except appid).

  • POST /content-monitor/:id/pause — pause (checks are not performed; nextCheckAt is moved far into the future).
  • POST /content-monitor/:id/resume — resume (status becomes pending again, check is scheduled for “now”).

The monitor record stores lastValue (snapshot of the last successful check — a JSON object with the rule values), lastError (last load/extraction error), lastCheckAt, nextCheckAt and alertedAt. Check history with extracted values and detected changes — GET /monitor-history/content_monitor/:id (see Active monitors) and the “History” tab in the monitor’s edit dialog.

  • Check interval: 60–86400 seconds (1 minute to 24 hours).
  • Load timeout: default 15 s, maximum 60 s.
  • Up to 20 rules per monitor.
  • Alert priority: 0–10; name length — 1–200, alertMessage — 1–2000 characters.
  • The page is loaded with a GET request without custom headers or body; up to 1 MB of the response is read. An HTTP code ≥ 400 is a check error.
  • Requests originate from the cloud: the URL must be reachable from the public internet; private-network, loopback and link-local addresses are rejected.
  • Browser mode (useBrowser) works only if the browser service is configured on the server.

Authorization — the X-Notifly-Key header with an API token (or a device token). Create, update, delete, pause and resume require write access for an API token; listing, test-rule and suggest-selector only need read. A token restricted to certain channels sees and changes only the monitors of those channels. Channel share tokens are not allowed to manage monitors (403).

MethodPathDescription
GET/content-monitorList of the user’s content monitors
POST/content-monitorCreate a monitor
POST/content-monitor/suggest-selectorSuggest mode + selector with AI by description
POST/content-monitor/test-ruleApply one rule to a page (dry-run), return value or error
PUT/content-monitor/:idUpdate settings (cannot change appid)
DELETE/content-monitor/:idDelete a monitor (together with its screenshots)
POST/content-monitor/:id/pausePause checks
POST/content-monitor/:id/resumeResume checks
GET/browser-shot/<key>Screenshot from alert extras (monitor owner only)

Content monitors can also be created and rules tested in plain words via MCP — for example, “watch the price on https://example.com/product/42 every hour”.

See also: Active monitors, HTTP monitors, Workflow monitors, Browser-workflow.