Skip to content

HTTP monitors (advanced)

The “light” http type in active monitors performs only GET <url> and compares the status code with 2xx (or expectedStatus). That’s enough for a simple health-check, but doesn’t cover cases like “send POST /api/login with a JSON body and an Authorization header, then check that the response contains "status":"ok" and code 200”.

An HTTP monitor is a separate entity with its own REST API that supports:

  • arbitrary method — GET, POST, PUT, PATCH, DELETE, HEAD;
  • arbitrary headers (headers) — a JSON object {"Header": "Value"};
  • request body (body) — for POST/PUT/PATCH;
  • response filtering — by status code, range of codes, substring/regex in body, value by JSONPath, and maximum response time;
  • two reaction modes — alert on a filter “match” or “mismatch”.

Interval, timeout, consecutive failures, alert/recovery texts and pausing work the same as for regular monitors.

FieldTypeRequiredDescription
appidnumberyesID of the channel to send notifications to
namestringyesMonitor name, 1–200 characters
methodstringyesGET, POST, PUT, PATCH, DELETE, HEAD (case-insensitive)
urlstringyesA valid http(s) URL pointing to a public address
headersstringnoJSON object of headers: {"Authorization": "Bearer ..."}
bodystringnoRequest body (for POST/PUT/PATCH)
intervalSecnumberyesInterval between checks, 30–86400 seconds
timeoutSecnumbernoTimeout for one attempt, default 10, maximum 30
filterModestringyesalert_on_match or alert_on_mismatch (see below)
filtersstringyesJSON array of filters (see Response filters); may be []
consecutiveFailsnumbernoHow many consecutive failures before alert, default 1, maximum 20
alertMessagestringyesNotification text when triggered, 1–2000 characters
alertTitlestringnoNotification title
alertPrioritynumbernoNotification priority, 0–10 (recovery is sent with it too)
recoveryTitlestringnoRecovery message title
recoveryMessagestringnoText when returning to normal (if empty — no recovery is sent)

URLs pointing to private, loopback or link-local addresses are rejected (400) — both on save and in the test request; redirects to such addresses are blocked too. Redirects to public addresses are followed, and filters apply to the final response.

filters is a JSON array of objects. Each object is one condition; within an object all specified fields must hold at the same time (AND), and the array as a whole matches if at least one object matches (OR). Available filter fields:

Filter fieldTypeWhat it checks
statusCodenumberExact HTTP code (0 = don’t check)
statusCodeMin / statusCodeMaxnumberRange of codes [min, max] inclusive (0,0 = don’t check; without max — up to 999)
bodyContainsstringSubstring that must be contained in the body
bodyRegexstringRegular expression (Go syntax) for the body
jsonPathstringPath in a JSON response: $.status, data.0.id; without jsonPathValue — must exist
jsonPathValuestringExpected value at jsonPath (string comparison: 200, true, ok)
maxDurationMsnumberMaximum allowed response time in ms (0 = don’t check)

For bodyContains, bodyRegex and jsonPath the body is read up to 64 KB. JSONPath is a simple $.a.b traversal with numeric array indexes $.a.0.b. An invalid bodyRegex is not rejected on save — such a filter simply never matches.

  • alert_on_mismatch — describe what a normal response looks like. If the actual response does not match the filters — the check fails. This is a whitelist logic and the most common scenario.
  • alert_on_match — describe a problematic response. If the actual response matches the filter — the check fails. Useful to catch specific errors.

A network error or timeout is a failure in either mode.

// filterMode = "alert_on_mismatch":
// normal — code 200 and JSON {"status":"ok"}; otherwise alert.
[
{ "statusCode": 200, "jsonPath": "$.status", "jsonPathValue": "ok" }
]
// filterMode = "alert_on_match":
// alert if the word "maintenance" appears in the body OR the code is in 5xx.
[
{ "bodyContains": "maintenance" },
{ "statusCodeMin": 500, "statusCodeMax": 599 }
]
// normal — responds faster than 800 ms and code 2xx (filterMode = "alert_on_mismatch"):
[
{ "statusCodeMin": 200, "statusCodeMax": 299, "maxDurationMs": 800 }
]

An HTTP monitor uses the same statuses as regular monitors: pending (created or resumed, no checks yet), up, degraded (there are failures, but the consecutiveFails threshold has not yet been reached), down and paused.

EventWhenText
AlertAfter consecutiveFails failed checks in a row, once per outage. The reason is appended in parentheses: response mismatch: status=… duration=…ms, alert condition matched: … or the network error textalertTitle + alertMessage
RecoveryOn the first successful check after down — only if recoveryMessage is set and an alert about the outage was actually sentrecoveryTitle + recoveryMessage

There is no alert spam: once an alert is sent, the monitor records the moment in the alertedAt field and sends nothing more while it stays down — checks continue and only record the status. The exception is re-arm: if you change check parameters via PUT /http-monitor/:id (method, URL, headers, body, interval, timeout, filterMode, filters, consecutiveFails), alertedAt is cleared and the monitor is checked right away; if the response is still bad, a fresh alert arrives. Editing the name, texts or priority does not shift the schedule and does not re-arm.

What else affects notifications — same as for regular monitors:

  • Maintenance windows: during a window checks run and are recorded in history, but the alert is deferred — if the response is still bad after the window, the very first check sends it. Down and back up within the window — neither alert nor recovery is sent.
  • Quiet hours suppress escalation to external channels for messages with priority below breakthroughPriority (8 by default).
  • The channel’s status page: going down 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.

Besides settings, the monitor record stores status, failCount, lastCheckAt, lastError, lastStatusCode, lastBodyPreview (first 512 bytes of the response), nextCheckAt and alertedAt. Check history — GET /monitor-history/http_monitor/:id (see Active monitors).

Before creating a monitor you can perform a one-time test request and inspect the response — nothing is saved. POST /http-monitor/test accepts method, url, headers, body, timeoutSec (default 10, up to 60) and returns statusCode, bodyPreview (first 2048 bytes), headers (JSON of response headers), durationMs and error (if the request failed). The test request does not apply filters.

Окно терминала
curl -X POST "$NOTIFLY_URL/http-monitor/test" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"method": "POST",
"url": "https://api.example.com/login",
"headers": "{\"Content-Type\": \"application/json\"}",
"body": "{\"user\":\"probe\",\"pass\":\"...\"}",
"timeoutSec": 10
}'
# → {"statusCode":200,"bodyPreview":"{\"status\":\"ok\"}","headers":"{...}","durationMs":134}
  1. Open app.notifly.ru → Monitors → Create → “Website or API is up” (or the HTTP requests tab → “Create HTTP monitor”).
  2. Specify the name, channel, method, URL, and optionally — headers and body. Use the “Test request” button to see what comes back.
  3. Configure the interval, timeout and number of consecutive failures.
  4. Describe the response filters and choose the filter mode.
Окно терминала
curl -X POST "$NOTIFLY_URL/http-monitor" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"appid": 12345,
"name": "API login health",
"method": "POST",
"url": "https://api.example.com/login",
"headers": "{\"Content-Type\": \"application/json\"}",
"body": "{\"user\":\"probe\",\"pass\":\"...\"}",
"intervalSec": 60,
"timeoutSec": 10,
"filterMode": "alert_on_mismatch",
"filters": "[{\"statusCode\":200,\"jsonPath\":\"$.status\",\"jsonPathValue\":\"ok\"}]",
"consecutiveFails": 2,
"alertMessage": "Login endpoint is not responding as expected!",
"alertPriority": 9,
"recoveryMessage": "Login works again."
}'

The fields headers, body and filters are passed as strings (JSON inside JSON), so in the curl examples their contents are escaped. PUT /http-monitor/:id accepts the same full set of fields (except appid — the channel cannot be changed).

If a Notifly MCP server is configured (see MCP), it’s enough to describe the task in words:

Create an HTTP monitor: POST to https://api.example.com/login with a JSON body, check every minute that the code is 200 and the response has status=ok, alert after 2 consecutive failures.

MethodPathDescription
GET/http-monitorList user’s HTTP monitors
POST/http-monitorCreate
POST/http-monitor/testOne-time test request (without saving)
PUT/http-monitor/:idUpdate settings (cannot change appid)
DELETE/http-monitor/:idDelete
POST/http-monitor/:id/pausePause checks
POST/http-monitor/:id/resumeResume: status pending, checked right away

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 and POST /http-monitor/test only need read. A token restricted to certain channels sees and changes only the monitors of those channels. Channel share tokens are not allowed on these endpoints (403). The monitor’s channel must be yours or shared with you with send permission.