Assess an x402 endpoint before agents pay it

Settlens runs x402 testing from the client's side: it requests your paid resource, decodes PAYMENT-REQUIRED, validates it with the official @x402/core SDK, checks that it is bound to the URL you requested and compares every advertised option with your recipient, network, asset and price policy. Passive by default. With the owner's written approval, one deliberately invalid payment checks that the resource fails closed.

Real output from the bundled demo target (npm run demo:server).

settlens scan (demo target)
settlens scan http://127.0.0.1:4402/secure
Settlens x402 assessment 86148897
Targets 1 · Active 0 · Settlement calls 0
 
127.0.0.1  GET http://127.0.0.1:4402/secure
13 pass · 0 fail · 0 warn · 0 skip
 
Total: 13 pass · 0 fail · 0 warn · 0 skip

In short

Settlens Assess tests an x402 V2 paid endpoint from the client's side: the HTTP 402 handshake, PAYMENT-REQUIRED validated with the official @x402/core SDK, the resource binding, every payment option, drift and your policy. With the owner's written approval, one invalid signature checks that it fails closed.

What a passive scan checks

A passive scan sends ordinary unauthenticated requests. It never sends a payment signature, and it refuses one if you try to add it as a custom header.

  • The 402 handshake

    The unauthenticated request must return HTTP 402 with a PAYMENT-REQUIRED header that decodes as Base64 JSON.

  • Official SDK and V2 shape

    The decoded object must pass the schema of the pinned @x402/core 2.28.0 SDK and carry x402Version 2, a resource and a non-empty accepts list.

  • Resource binding

    resource.url must equal the URL you scanned, path and query included, so a payment cannot be steered to another resource.

  • Every payment option

    Scheme, CAIP-2 network, positive atomic amount, asset, payTo and maxTimeoutSeconds are validated. Duplicates and windows over one hour are flagged.

  • Drift between requests

    Identical requests (two by default) must return the same resource, options and extensions, so silent price or recipient changes surface.

  • Transport hygiene

    HTTPS outside loopback, redirects reported and never followed, response bodies read up to 1 MiB and never copied into the report.

  • Your payment policy

    Allowed schemes, networks, recipients, assets and a maximum amount. In a scan, every advertised option must comply.

  • Facilitator coverage

    With --facilitator, Settlens reads GET /supported and checks that each advertised scheme and network is covered, with signer metadata.

    Facilitator checks

How a scan runs

  1. Request the resource without payment

    Settlens sends the configured method (GET by default) with no payment header, an 8-second timeout and redirects disabled. Custom headers are only sent to hosts you allowlist.

  2. Decode and validate PAYMENT-REQUIRED

    The header is size-checked, Base64-decoded and parsed as JSON, then validated twice: by the official @x402/core 2.28.0 schema and by Settlens' own V2 core-shape checks.

  3. Check binding and each option

    resource.url is compared with the URL under test, and every accepts entry is validated field by field.

  4. Repeat to detect drift

    The same request is sent again (--repeat, two samples by default) and the terms are compared, ignoring known ephemeral fields.

  5. Apply your policy

    Every advertised option is compared with the allowed schemes, networks, recipients, assets and maximum amount you set.

  6. Write the evidence

    The CLI prints a summary and writes JSON, HTML and SARIF reports. The JSON report carries a SHA-256 integrity digest you can check with verify-report or sign as evidence.

Passive scan and authorized probe

Both modes run the same passive checks first. The active probe adds exactly one request to the resource and, when a facilitator is configured, one call to its /verify endpoint.

Passive requests run on every scan. The blue exchanges run only with --active --authorized --authorization-ref.
What each mode sends
BehaviorPassive scanAuthorized active probe
Unauthenticated requests to the resource2 by default2 by default
Sends a PAYMENT-SIGNATUREOne, deliberately invalid
Calls facilitator /verifyOnce, with --facilitator
Calls facilitator /settle
Needs a wallet or private key
Requires the owner's approval reference

Example: a mainnet policy against a testnet endpoint

The demo endpoint advertises 10000 atomic units of a token on Base Sepolia (eip155:84532). A policy written for Base mainnet with a lower ceiling catches both differences.

settlens scan with a local policy
settlens scan http://127.0.0.1:4402/secure \
    --expect-scheme exact --expect-network eip155:8453 \
    --expect-pay-to 0x209693Bc6afc0C5328bA36FaF03C514EF312287C \
    --max-amount 5000 --no-write
Settlens x402 assessment 3f9a8296
Targets 1 · Active 0 · Settlement calls 0
 
127.0.0.1  GET http://127.0.0.1:4402/secure
15 pass · 2 fail · 0 warn · 0 skip
  FAIL high     Networks match the local policy
       Unexpected networks: eip155:84532.
  FAIL high     Amounts stay within the approved ceiling
       1 payment amount(s) exceed 5000.
 
Total: 15 pass · 2 fail · 0 warn · 0 skip

Both failures are high severity, so the run exits with code 1 at the default --fail-on high threshold. That is the same signal the CI gate uses to block a merge.

The recipient matched, so policy.recipient passed. EVM addresses are compared case-insensitively; other formats, such as Solana base58 addresses, must match exactly.

Without a policy the same endpoint passes all 13 checks, as in the transcript at the top of this page.

Write the policy once

Flags cover one-off scans; the configuration file keeps the policy reviewed and versioned next to the endpoint it describes.

Policy fields
Configuration fieldCLI flagCheck
expected.schemes--expect-schemepolicy.scheme
expected.networks--expect-networkpolicy.network
expected.payTo--expect-pay-topolicy.recipient
expected.assets--expect-assetpolicy.asset
expected.maxAmount--max-amountpolicy.maximum_amount
expected.resourceUrlConfiguration onlybinding.resource_url
JSONsettlens.config.json (excerpt)
{
  "expected": {
    "resourceUrl": "https://merchant.example/api/data",
    "schemes": [
      "exact"
    ],
    "networks": [
      "eip155:8453"
    ],
    "payTo": [
      "0xYourReviewedRecipient"
    ],
    "assets": [
      "0xYourReviewedTokenContract"
    ],
    "maxAmount": "10000"
  }
}

A scan is stricter than an agent

An agent pays with one option, so its MCP preflight only needs one acceptable option. A scan tests the server, so every advertised option must be within policy. Amounts are atomic units: pair maxAmount with an asset allowlist. See how to write an agent spending policy.

The authorized active probe

A passive scan shows what the resource advertises. Only an invalid payment shows what it does with one. Settlens sends exactly one, and only when you prove you are allowed to.

Three safety gates

--active alone is refused. The CLI also requires --authorized, your confirmation that the endpoint owner approved the test, and --authorization-ref, the ticket, email or scope identifier of that approval. Missing either one stops the run with exit code 2 before any request is sent. The configuration file has no field that turns active mode on, so a changed config cannot enable it.

What the probe sends

Settlens builds a payment payload from the first advertised option and makes it unusable: on EVM networks a 65-byte all-zero signature from the zero address with a zero nonce, on Solana 64 zero bytes in place of a transaction, on other networks a placeholder string. It sends that payload once as PAYMENT-SIGNATURE. The probe needs a decodable V2 PAYMENT-REQUIRED with at least one option first; otherwise it is skipped.

What must happen

  • The resource must not answer with a 2xx status. A 2xx is a critical payment bypass.
  • It should answer 400 or 402, as the x402 HTTP transport specifies.
  • With --facilitator, Settlens posts the same payload to /verify, which must return a VerifyResponse with isValid: false.

The approval reference is recorded in the report. The report scope also declares zero settlement calls and zero private keys used: Settlens has no code path that settles or signs a payment, so these are fixed values, not measurements.

Safety gates (no request is sent)
settlens scan https://merchant.example/api/data --active
Settlens error: Active testing requires --authorized to confirm explicit owner approval.
settlens scan https://merchant.example/api/data --active --authorized
Settlens error: Active testing requires --authorization-ref with a traceable owner approval reference.
Bypass detected on the demo target
settlens scan http://127.0.0.1:4402/bypass \
    --active --authorized --authorization-ref DEMO-APPROVAL-002 --no-write
Settlens x402 assessment bc909b8c
Targets 1 · Active 1 · Settlement calls 0
 
127.0.0.1  GET http://127.0.0.1:4402/bypass
13 pass · 1 fail · 1 warn · 0 skip
  FAIL critical Invalid payment authorization is denied
       The endpoint returned HTTP 200 after an invalid payment authorization.
  WARN medium   Invalid payment uses a standard failure status
       Expected HTTP 400 or 402 for invalid payment, received HTTP 200.
 
Total: 13 pass · 1 fail · 1 warn · 0 skip

Authorized testing only

Run the active probe only against endpoints you own or have written permission to test. The demo target's /bypass route is built to unlock with any signature, which is exactly what the probe exists to catch.

JSONReport excerpt: authorized probe against the demo /secure route
{
  "scope": {
    "targetCount": 1,
    "activeTargetCount": 1,
    "settlementCalls": 0,
    "privateKeysUsed": 0
  },
  "targets": [
    {
      "mode": "active-authorized",
      "authorization": {
        "confirmed": true,
        "reference": "DEMO-APPROVAL-001"
      },
      "checks": [
        {
          "id": "security.invalid_signature_fail_closed",
          "status": "pass",
          "severity": "critical",
          "message": "The intentionally invalid payment was denied with HTTP 402.",
          "evidence": { "status": 402, "responseBytes": 47 }
        }
      ]
    }
  ]
}

Checks in this stage

Rendered from the same catalog the CLI and the MCP server use. Severities are defaults; the check reference explains why each one matters and how to fix it.

Handshake, schema, binding and stability (14 checks)
CheckDefault severityWhat it verifies
transport.reachable
Endpoint is reachable
highSettlens received an HTTP response from the target URL within the timeout (8000 ms by default). Redirects are not followed.
transport.status_402
Unauthenticated request requires payment
highA request without any payment header returns HTTP 402 Payment Required.
transport.payment_required_header
PAYMENT-REQUIRED header is present
highThe 402 response carries the PAYMENT-REQUIRED header defined by the x402 V2 HTTP transport.
transport.payment_required_encoding
PAYMENT-REQUIRED decodes as Base64 JSON
highThe PAYMENT-REQUIRED value is at most 131072 characters, uses only Base64 or Base64URL characters and decodes to valid JSON nested at most 64 levels deep.
transport.response_body_limit
Response body stays within the evidence limit
mediumThe 402 response body fits within the 1 MiB processing limit. Settlens stops reading at the limit.
transport.https
Transport protects payment metadata
mediumThe endpoint URL uses HTTPS. Plain HTTP is accepted only for loopback hosts (localhost, 127.0.0.1, ::1) used in local testing.
transport.redirect
Payment endpoint does not redirect unexpectedly
mediumReported as a warning when the endpoint answers with a 3xx redirect. Settlens never follows redirects and records the sanitized Location instead.
schema.official_x402_sdk
Official x402 SDK accepts PaymentRequired
highThe decoded PaymentRequired object passes the parsePaymentRequired schema of the pinned official @x402/core 2.28.0 SDK.
schema.payment_required
PaymentRequired matches the x402 V2 core shape
highx402Version equals 2, resource is an object whose url is an absolute http(s) URL, and accepts is a non-empty array.
binding.resource_url
Payment requirement is bound to the requested resource
highresource.url equals the URL that was requested, including path and query (the fragment is ignored). In evaluate_payment_requirements this check is a warning when no resource URL is supplied, because the binding cannot be confirmed.
schema.payment_requirements
Accepted payment requirements are well formed
highEvery accepts entry has a non-empty scheme, a CAIP-2 network, a positive integer atomic amount, a non-empty asset and payTo, and a positive maxTimeoutSeconds. On eip155 networks payTo must be a 20-byte hex address.
schema.duplicate_requirements
Payment choices are unambiguous
lowNo two accepts entries are identical.
freshness.timeout_window
Authorization windows are reasonably bounded
mediumEvery maxTimeoutSeconds value is at most 3600 seconds (one hour).
stability.payment_requirements
Payment requirements remain stable
mediumRepeated identical unauthenticated requests (2 by default) return the same resource, accepts and extensions, ignoring the known ephemeral fields recentBlockhash and lastValidBlockHeight. Skipped when only one sample is available.
Policy (5 checks, each evaluated only when its field is set)
CheckDefault severityWhat it verifies
policy.scheme
Schemes match the local policy
highEvery advertised scheme is in the policy's allowed schemes (case-insensitive). Evaluated only when a scheme policy is set. In the agent tools (evaluate_payment_requirements, scan_endpoint) it summarizes the options instead: pass when every option complies, warn when some do not, fail when none does; the decision then rests on policy.acceptable_option.
policy.network
Networks match the local policy
highEvery advertised CAIP-2 network is in the policy's allowed networks (case-insensitive). Evaluated only when a network policy is set. In the agent tools (evaluate_payment_requirements, scan_endpoint) it summarizes the options instead: pass when every option complies, warn when some do not, fail when none does; the decision then rests on policy.acceptable_option.
policy.recipient
Recipients match the local policy
highEvery advertised payTo is in the policy's allowed recipients (EVM hex addresses case-insensitively, other formats such as Solana base58 exactly). Evaluated only when a recipient policy is set. In the agent tools (evaluate_payment_requirements, scan_endpoint) it summarizes the options instead: pass when every option complies, warn when some do not, fail when none does; the decision then rests on policy.acceptable_option.
policy.asset
Assets match the local policy
highEvery advertised asset (token contract address or mint) is in the policy's allowed assets (EVM hex addresses case-insensitively, other formats such as Solana mints exactly). Evaluated only when an asset policy is set. In the agent tools (evaluate_payment_requirements, scan_endpoint) it summarizes the options instead: pass when every option complies, warn when some do not, fail when none does; the decision then rests on policy.acceptable_option.
policy.maximum_amount
Amounts stay within the approved ceiling
highEvery advertised atomic amount is at most the policy's maxAmount. Amounts are compared in atomic units, so pair maxAmount with an asset policy. Evaluated only when maxAmount is set. In the agent tools (evaluate_payment_requirements, scan_endpoint) it summarizes the options instead: pass when every option complies, warn when some do not, fail when none does; the decision then rests on policy.acceptable_option.
Authorized active probe (6 checks)
CheckDefault severityWhat it verifies
security.invalid_signature_fail_closed
Invalid payment authorization is denied
criticalWith explicit authorization, Settlens sends one intentionally invalid PAYMENT-SIGNATURE (for example an all-zero EVM signature) and expects the resource not to answer with a 2xx status.
transport.invalid_payment_status
Invalid payment uses a standard failure status
mediumThe response to the intentionally invalid payment is HTTP 400 or 402.
safety.active_authorization
Active probe has explicit authorization
highAn active probe was requested together with the owner-authorization confirmation and a non-empty authorization reference. Otherwise no probe is sent and this check fails.
facilitator.verify_reachable
Facilitator /verify is reachable
mediumDuring an authorized active probe, POST <facilitator>/verify with the intentionally invalid payload completed. Reported as a warning only when it could not complete.
facilitator.verify_response_schema
Facilitator /verify returns a conformant VerifyResponse
highThe /verify answer to the intentionally invalid payload is a JSON object with a boolean isValid field.
security.facilitator_rejects_invalid_signature
Facilitator /verify rejects an invalid signature
critical/verify returns isValid=false for the intentionally invalid, all-zero signature. Settlens never calls /settle.

What Assess does not do

  • No settlement, no funds. Settlens never calls /settle, never creates a valid payment and never holds a wallet or payment key.
  • No route discovery. You name the URLs. Settlens does not crawl or guess paid routes.
  • Sampled behavior. A scan sees a few requests in a short window. It cannot prove every server-side code path, rate limit or tenant behaves the same.
  • One invalid payload class. The active probe proves an obviously forged payment is refused. It is not a full test of your signature verification; use offline verification to check captured authorizations.
  • No on-chain view. Assess does not read balances, nonces, token contracts or transactions.
  • Redirects stop the scan's view. A 3xx is reported and not followed; scan the canonical URL instead.
  • Not a certification. A report is evidence of observed behavior, not a security audit or legal opinion. See the methodology.

Questions about assessments

Does a passive scan send a payment?

No. It sends ordinary requests without any payment header. If you add a PAYMENT-SIGNATURE header yourself, the CLI refuses to run (exit code 2).

How many requests does a scan make?

Two to the resource by default (set with --repeat). With --facilitator, one more GET to /supported. The authorized probe adds one request to the resource and, with a facilitator, one POST to /verify.

Can I enable the active probe from the configuration file?

No. Active mode exists only as command-line flags, and it needs --active, --authorized and --authorization-ref together. If --active comes without the other two, the CLI stops before sending any request.

Can a valid payment ever be sent by mistake?

No. The probe payload carries an all-zero signature, zero bytes or a placeholder instead of a real authorization, Settlens has no payment key to sign with, and it never calls /settle.

Does a passing scan mean my endpoint is secure?

It means the endpoint behaved correctly for every property tested during the scan. It does not cover untested code paths, on-chain behavior or your wider application.

Which x402 version is supported?

x402 V2 over HTTP: the PAYMENT-REQUIRED and PAYMENT-SIGNATURE headers, validated with the official @x402/core 2.28.0 SDK. Responses without a V2 PAYMENT-REQUIRED header, including x402 V1 responses, fail these checks. See the x402 V1 to V2 migration guide.

Sources

  1. x402 protocol specification, version 2x402 Foundation · github.com
  2. x402 V2 HTTP transportx402 Foundation · github.com
  3. CAIP-2: Blockchain ID SpecificationChain Agnostic Standards Alliance · github.com

Assess one paid endpoint first

A scoped pilot on one endpoint, with your policy, an authorized probe if you approve it, and a report your team can verify.