Documentation menu

Monitor

Scan production payment endpoints on a schedule, compare each run with the previous one and alert when a check regresses.

In short

settlens monitor runs a passive scan of every configured target, compares each check with the previous run stored in a state file and alerts on regressions, new findings and removed checks. It exits 1, and can POST a JSON webhook, when an alert's check severity reaches the fail-on threshold (high by default).

On this page

settlens monitor turns a point-in-time scan into continuous assurance. Each run performs the same passive scan as settlens scan on every target of a configuration file, compares the results with the previous run stored in a state file, and reports what changed: a payment endpoint that starts advertising a new price, a different recipient or an unsupported network is caught on the next run. Monitoring never sends a payment or runs the active probe. Optional journal mode keeps an exportable observation history.

Each run compares with the run before it, then becomes the next baseline.

Private workspace and persistent history

The optional private workspace at /dashboard supports endpoint configuration, manual and scheduled scans, change history, remediation hints and JSON or HTML exports. It is a single-owner workspace protected by a dedicated token exchanged for a one-hour session; it is not a multi-tenant customer portal. Remote scans accept only public destinations and no custom headers.

In the private workspace, edit endpoint names and policies while retaining history, review observed terms before approval, acknowledge incidents, add comments, filter by severity and inspect or retry notification deliveries. Open incidents remind daily; acknowledgement pauses reminders. Delivery uses bounded persistent retries and can duplicate an email if a remote acceptance response was lost.

For CLI history, use --history .settlens/monitor.json instead of --state. The journal separates latest observations from owner-approved terms and keeps up to 200 runs from the last 90 days, subject to its 8 MiB bound. Active incidents gate CI until passing observations establish recovery; missing checks do not prove recovery. Export with settlens history --history .settlens/monitor.json --format html --out history.html. SHA-256 digests detect edits but do not authenticate the observer.

Payment fields are compared using fingerprints; raw terms are not stored. Option reordering and rotating Solana blockhash fields do not cause alerts. Outages retain the last observable terms for comparison on recovery. When SMTP is configured, blocking changes go to contact@settlens.com with the subject prefix [Settlens][ALERTE]; pilot requests use [Settlens][PILOTE].

Usage

ShellSyntax
settlens monitor --config <file> [--state <file>] [--webhook <url>] [--fail-on <severity>] [--timeout <ms>]
  • --config (required): the same file format as settlens check. failOn applies when --fail-on is not given; output is ignored.
  • --state: where the previous run is read and the new one written. Without it, nothing is remembered and every run is a first run.
  • --webhook: URL that receives a JSON POST when at least one alert reaches the threshold.
  • --fail-on: lowest severity that makes an alert blocking: low, medium, high, critical or none. Default high. Any other value stops the command with exit code 2 before anything is scanned.
  • --timeout: per-request timeout in milliseconds. Each target's repeat comes from the config.
  • Custom headers follow the same allowlist rule as other commands: set SETTLENS_ALLOWED_HOSTS.

A baseline, then a regression

The demo server's /drift endpoint alternates its price between 10000 and 25000 atomic units on successive requests. With repeat: 1, each run sees one price, so a ceiling of 10000 passes on one run and fails on the next:

JSONsettlens.config.json
{
  "version": 1,
  "failOn": "high",
  "targets": [
    {
      "name": "priced-api",
      "url": "http://127.0.0.1:4402/drift",
      "repeat": 1,
      "expected": {
        "networks": ["eip155:84532"],
        "payTo": ["0x209693Bc6afc0C5328bA36FaF03C514EF312287C"],
        "maxAmount": "10000"
      }
    }
  ]
}
ShellCompare two observations
settlens monitor --config settlens.config.json --history .settlens/monitor.json
settlens monitor --config settlens.config.json --history .settlens/monitor.json

The first run records a baseline. The next run detects the changed amount even without a price policy. If the amount also exceeds the configured ceiling, the policy regression is a separate alert.

Coverage that disappears is an alert too. Delete maxAmount from the configuration and the next run no longer evaluates the ceiling, so the check that guarded the price is reported as removed:

The ceiling removed from the configuration (exit 1)
settlens monitor --config settlens.config.json --state state.json
 
priced-api (GET http://127.0.0.1:4402/drift)
  ALERT [high] policy.maximum_amount: removed (was pass): coverage lost — Amounts stay within the approved ceiling
 
1 blocking alert(s) at or above high.

What raises an alert

Checks are compared by id. Statuses are ranked pass = skip < warn < fail.

How changes between two runs are reported
ChangeConsoleAlert
A check moves up the ranking (pass or skip to warn or fail, warn to fail)ALERT or noteYes, regression
A check absent from the previous run is now warn or failALERT or noteYes, new finding
A check moves down the ranking (fail to warn or pass, warn to pass)resolvedNo
A check keeps the same status, even failnothingNo
A check present before is missing nowALERT or note: removed (was …): coverage lostYes, removed
First run for a targetbaseline recordedNo

An alert is blocking, printed as ALERT, when the severity of its check is at or above --fail-on; below the threshold it is printed as note and does not change the exit code. none never blocks. Payment-term changes are also alerts: amount, recipient, asset, network, scheme, resource binding and version changes are high severity; timeout and extra metadata changes are medium severity. The rule applies to every alert kind. Unlike scan and check, a new warning of high severity is blocking at the default threshold. A removed check uses the severity it had in the previous run.

Legacy state mode reports changes

With legacy --state, a check that keeps failing alerts once, on the run where it changed. Journal mode instead maintains persistent incidents and gates CI until recovery. The state file then records the failure as the new baseline. To fail a job on everything that is currently wrong, run settlens check alongside the monitor.

State file

The state file is a JSON object keyed by each target's method and sanitized URL, for example GET http://127.0.0.1:4402/drift. For every target it keeps the compact list of checks, the summary and the time of the run. It is read at the start of a run and rewritten at the end, so the baseline is always the previous run. Excerpt after the regression above (3 of 16 checks kept):

JSONstate.json (excerpt)
{
  "GET http://127.0.0.1:4402/drift": {
    "checks": [
      { "id": "transport.reachable", "status": "pass", "severity": "high", "title": "Endpoint is reachable" },
      { "id": "transport.status_402", "status": "pass", "severity": "high", "title": "Unauthenticated request requires payment" },
      { "id": "policy.maximum_amount", "status": "fail", "severity": "high", "title": "Amounts stay within the approved ceiling" }
    ],
    "summary": { "pass": 14, "fail": 1, "warn": 0, "skip": 1 },
    "observedAt": "2026-10-13T11:02:43.812Z"
  }
}
  • A missing state file starts an empty baseline. An unreadable or invalid file stops the command rather than silently resetting coverage. Writes are atomic and protected by a fail-fast writer lock.
  • Targets are keyed by method and URL, so a GET and a POST target on the same URL keep separate baselines. A state file written by Settlens 0.4.0, keyed by URL only, is still read for a URL that only one target uses, and the entry is rewritten under the new key.
  • Legacy state files hold check ids, statuses, severities, titles and payment-term fingerprints. Journal mode adds validated payment field values for review, approved references, incidents, comments and notification delivery jobs; it never stores request headers, raw response bodies or transport credentials. Keep it with the monitor's working files and do not edit it by hand.

Webhook

With --webhook, Settlens sends one POST with Content-Type: application/json when the run has at least one blocking alert. This is the payload received for the regression above:

JSONWebhook body
{
  "product": "Settlens Monitor",
  "alerting": 1,
  "failOn": "high",
  "runs": [
    {
      "method": "GET",
      "url": "http://127.0.0.1:4402/drift",
      "alerts": [
        {
          "kind": "regression",
          "id": "policy.maximum_amount",
          "from": "pass",
          "to": "fail",
          "severity": "high",
          "title": "Amounts stay within the approved ceiling"
        }
      ]
    }
  ]
}
  • alerting counts blocking alerts and failOn is the threshold used. runs lists every target with its method, url and all of its alerts, including those below the threshold. Each alert has a kind: regression (with from and to), new or removed (with the previous status).
  • The request times out after 5 seconds and does not follow redirects. A failed delivery is printed to stderr as Monitor webhook failed: … and does not change the exit code.
  • With --webhook-secret-env MONITOR_WEBHOOK_SECRET, the request carries x-settlens-timestamp and x-settlens-signature (sha256= plus HMAC-SHA256 of timestamp.body). The receiver should verify the exact bytes and reject old timestamps. An HTTP error is a failed delivery; details and secret URLs are not logged.

Scheduling

Run the monitor from cron, a systemd timer or a CI schedule, in a directory where the state file persists between runs:

Shellcrontab: every 15 minutes
*/15 * * * * cd /opt/settlens-monitor && node /opt/settlens/src/cli.js monitor --config settlens.config.json --state settlens.state.json --webhook https://hooks.example.com/settlens >> monitor.log 2>&1

On a CI runner the workspace starts empty, so restore the state file at the start of each run and save it at the end (for example as a cache or an artifact); otherwise every run is a first run and nothing is ever compared. Each run makes repeat requests per target, two by default, so choose an interval the endpoint owner is comfortable with.

Exit codes

settlens monitor exit codes
CodeMeaning
0No blocking alert: first run, no change, resolved checks, or alerts below the threshold.
1At least one alert at or above --fail-on.
2Usage or configuration error, for example a missing --config, an invalid --fail-on or a custom header to a host that is not allowlisted.

Questions

Does Monitor send payments or run the active probe?

No. Every monitor run is a passive scan: ordinary unauthenticated requests, no PAYMENT-SIGNATURE, no call to /settle, no wallet or key. Monitor never runs the active probe, even when --active, --authorized and --authorization-ref are given.

Why did I not get a second alert for a check that is still failing?

Monitor alerts on changes between consecutive runs. After the run that raised the alert, the failure becomes the baseline. Use settlens check, or the GitHub Action, to gate on the current state.

Does Monitor write reports?

No. It prints a console summary and updates the state file. Run settlens check when you need JSON, HTML or SARIF evidence for a given moment.

Related: the checks that can regress are listed in the check catalog, the Monitor product page explains where it fits, and Detecting x402 payment drift shows what each kind of change looks like. To set up monitoring of your own endpoints, request a pilot.

Last updated