This builds on a basic monitor. If you don't have one yet, start with setting up website uptime monitoring: everything there, from the failure threshold to automatic recovery, works the same way here.
Why the status code isn't enough
Three things a status-code check misses:
- The endpoint needs a token. Without one, a protected
/healthanswers401and the monitor is down all the time, so it gets switched off and protects nothing. - The app is up and its dependencies aren't. Many health endpoints answer
200with{"status": "degraded"}when the database or a queue is unreachable. - Something else answered. A load balancer or CDN serving a maintenance page returns
200with HTML instead of your JSON.
Headers fix the first. Response assertions fix the other two: they read the JSON body and fail the check when a field doesn't say what it should. A body that isn't JSON at all fails every assertion, which is how the maintenance page gets caught.
Set it up
The examples use a health endpoint that returns this when everything is fine:
{
"status": "ok",
"version": "2.14.0",
"checked_at": "2026-10-04T09:12:44Z",
"services": { "db": "ok", "redis": "ok" }
}
-
Open Advanced settings on the monitor
Edit an existing monitor, or add a new one pointing at your health endpoint, and expand Advanced settings at the bottom of the form.
Method stays
GETfor almost every health endpoint.POST,PUTandPATCHlet you send a body, which goes out as JSON unless you set your ownContent-Typeheader.HEADskips the body entirely, so it can't be combined with assertions. -
Add the token as an encrypted header
Under Request headers, use the preset that matches how your API authenticates: Bearer token, API key or Basic auth. Each one adds an encrypted header. Paste the value and leave the lock closed.
- Bearer token adds
Authorizationalready starting withBearer. Paste the token after it; the value is masked, so use the eye icon to check the space is there. - API key adds
X-Api-Key. Rename it if your API expects another header name. - Basic auth asks for a username and password and stores the encoded
Authorizationheader for you.
Give the monitor its own token with read-only access to the health endpoint, not a personal or admin key. If it ever needs revoking, nothing else breaks. Credentials written into the URL, like
https://user:pass@…, are refused for the same reason: a URL gets shown and logged, a secret header doesn't. - Bearer token adds
-
Paste a healthy response
Under Response assertions, use Paste a healthy response and paste what the endpoint returns on a good day. Each field becomes an assertion.
Fields that change on every call only get checked for presence: numbers, dates, IDs and lists. Everything else must match exactly. The example above becomes:
Field Check $.statusequals ok$.versionequals 2.14.0$.checked_atexists $.services.dbequals ok$.services.redisequals ok -
Remove the assertions that would break on a normal day
Delete anything that changes without anything being wrong, such as a version string that moves on every deploy. Keep the fields that say whether each dependency is healthy.
Here that means deleting
$.version: left in, your next release would page the team. Ask of each row "if this changed, would something be broken?" and keep only the yeses. Fields you don't list are ignored, so the endpoint can grow new ones without breaking the monitor. -
Save, then break an assertion on purpose
Save the monitor and wait for a passing check. Then change one expected value to something the endpoint never returns, save, and wait for the Down alert. Put the value back and the alert resolves on the next passing check.
For example, set
$.services.dbto equalok-test. The monitor's recent checks show the reason in the Error column,Assertion failed: path '$.services.db' should equal "ok-test", and the alert carries the same text. A failed assertion is sent as a medium severity alert. A connection failure or wrong status code is high, since nothing answered at all. To see the alert sooner, use a 1 minute interval and a failure threshold of 1 while you test, then set them back.
Writing assertions
Each assertion has a path into the JSON, an operator and, for two of the operators, a value.
| Operator | Passes when |
|---|---|
| equals | The field is exactly the value: same type, same content. |
| contains | Text includes the value, a list has an item matching it, or an object has at least the fields you give. |
| exists | The field is present, even if it's null. |
| not empty | The field is present and isn't null, "", [] or {}. 0 and false pass. |
- Paths start at
$, which the form shows for you. Nest with dots and pick list items by position:services.db,checks[0].name. Field names can use letters, digits,_and-. - Values are read as JSON.
200is a number,truea boolean,okplain text. If the endpoint returns the text"200", type it with quotes, or equals fails on the type. - The JSON editor (the Visual / JSON switch) takes the shape you expect as one object:
{ "status": "ok", "services": { "db": "ok" } }passes when the response contains at least those fields with those values. For any other check, give a list of rules such as[{ "path": "$.checked_at", "operator": "exists" }]. - Up to 20 assertions per monitor, and the response must be under 1 MB.
How the token is kept
- Encrypted at rest, and write-only. Once saved, a secret header is never shown again: not in the form, not in the API. You can replace it, not read it back. The form says "Saved encrypted · type to replace".
- It doesn't follow redirects to other sites. If the endpoint redirects to a different origin, the check fails with "Redirect to a different origin blocked" instead of handing your token to someone else. Point the monitor at the final URL.
- Changing the URL to another origin asks for it again. Move a monitor from
api.example.comtoapi.other.comand saving asks you to re-enter each secret, so a stored token can't be pointed at a new host without someone typing it. - Alerts never quote the response. A failed assertion describes the rule, not the value received, because an endpoint can echo a request header back into its body.
- Prefer plain-text headers for anything that isn't secret, such as
Accept: they stay visible and editable. Click the lock to switch a header between plain and encrypted.
If a check fails unexpectedly
| What you see | Likely cause |
|---|---|
| Credentials rejected by the target | The token is wrong, expired or sent under the wrong header name. With Bearer, check the value still starts with Bearer followed by a space. |
| response body is not valid JSON | Something other than your API answered, such as a maintenance page or login screen, or the endpoint returns plain text. Check the URL, and that the token reached it. |
| An equals check fails while the response looks right | The type differs: 200 versus "200", or true versus "true". Add or remove the quotes. |
| Alerts after every deploy | An assertion checks something that changes on release, like a version or build number. Remove it, or use exists instead. |
| Redirect to a different origin blocked | The URL redirects to another domain or subdomain while carrying a secret. Use the final URL directly. |
| Saving asks to re-enter a secret | You changed the URL to a different origin. Type the value again, or put the old URL back. |
| "HEAD has no body" when saving | Assertions need a body. Switch the method to GET. |
| Response body exceeds 1024 KB | The endpoint returns too much. Point the monitor at a smaller health route. |
Catch the outage a 200 hides
Free during early access. Unlimited users, teams, alerts and monitors — no credit card.