For developers · free · no key

Five minutes from zero to a verdict.

The free gate answers one question before an agent signs anything: “If I sign this, is it what I think it is?” One HTTP call returns PASS / CAUTION / BLOCK, the reasons, the checks that ran, the checks that did not, and a short-lived receipt bound to the exact intent. No account, no API key, no wallet.

01 · A good payment

One curl. A real PASS.

This is a canonical x402 payment request: 0.01 USDC on Base mainnet, a known payee, a known resource. Paste it as-is.

POST /preflight · free

curl -sS -X POST https://verify402.onrender.com/preflight \
  -H "content-type: application/json" \
  -d '{
    "kind": "payment",
    "payment": {
      "scheme": "exact",
      "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "10000",
      "payTo": "0xAeEF086DD4e436c69d6d0F684b4c9F43392df9cb",
      "resource": "https://verify402.onrender.com/verify",
      "maxTimeoutSeconds": 60
    },
    "policy": { "maxAmountUsd": 0.25 }
  }'

amount is in raw units: 10000 = 0.01 USDC (6 decimals). Free hosting sleeps: the first call after a pause can take ~30 seconds.

Response · abbreviated

{
  "verdict": "PASS",
  "sign_allowed": true,
  "requires_ack": false,
  "reasons": [],
  "checked": ["scheme", "network", "asset", "amount", "payee", "resource", "timeout", "block_freshness"],
  "notChecked": ["recipient reputation", "sanctions screening", "payment signature / signed payload", "facilitator settlement", "off-chain promises", "counterparty identity / tool metadata", "prompt injection / hidden Unicode in tool output", "payer_balance"],
  "block": { "number": 52814257, "ageSeconds": 0 },
  "receipt": { "id": "pf_…", "expiresAt": "…", "signature": "…" }
}

Your block number will differ. sign_allowed is simply verdict !== "BLOCK"; a CAUTION still needs a decision.


02 · A bad signature

The same gate says no to an unlimited approval.

This calldata is USDC approve(spender, 2^256-1) — the classic “let this contract take everything” signature. With forbidUnlimitedApproval set, it is a hard block.

POST /preflight · free

curl -sS -X POST https://verify402.onrender.com/preflight \
  -H "content-type: application/json" \
  -d '{
    "kind": "transaction",
    "chainId": 8453,
    "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "data": "0x095ea7b30000000000000000000000001111111111111111111111111111111111111111ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "value": "0",
    "policy": { "forbidUnlimitedApproval": true }
  }'

Response · abbreviated

{
  "verdict": "BLOCK",
  "sign_allowed": false,
  "reasons": [
    {
      "code": "unlimited_approval",
      "severity": "block",
      "message": "approve grants an unlimited allowance and policy.forbidUnlimitedApproval is set"
    }
  ]
}

Add "from": "0xYourPayerAddress" to enable simulation. Without it the response also carries a simulation_skipped_no_from caution — the gate names what it could not check.


03 · The receipt

Verify before you trust it.

Every verdict carries an HMAC-signed receipt bound to the exact normalized intent and block. POST /preflight/verify recomputes the signature and checks expiry. A tampered copy returns bad_signature.

Node · verify, then sign

const gate = await fetch("https://verify402.onrender.com/preflight", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify(intent),
}).then((r) => r.json());

const check = await fetch("https://verify402.onrender.com/preflight/verify", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ receipt: gate.receipt }),
}).then((r) => r.json());

if (!check.valid) throw new Error(`receipt rejected: ${check.reason}`);

Receipts expire after 90 seconds by default (15–300 configurable). Re-call the gate instead of reusing an expired one. If the chain cannot be read, the gate returns 503 and no receipt — fail-closed.


04 · Inside the agent

One MCP tool, no wallet.

Run with npx, or add the server to Claude Desktop, Cursor, OpenCode or any stdio MCP client. With no EVM_PRIVATE_KEY, only preflight_gate is registered — nothing can be charged. Published on npm as verify402-mcp and listed on the official MCP Registry.

Run via npx · free gate

npx -y verify402-mcp --help

.cursor/mcp.json · free-only

{
  "mcpServers": {
    "verify402": {
      "command": "npx",
      "args": [
        "-y",
        "verify402-mcp"
      ],
      "env": { "VERIFY402_URL": "https://verify402.onrender.com" }
    }
  }
}

The MCP tool is advisory. Enforce it where the signing happens.

The bridge cannot stop an agent that ignores the answer. Use the dependency-free reference executor: it verifies the receipt and refuses to call your signer on BLOCK, a missing/expired receipt, or a failed verify. It holds no key.


05 · What I need back

One thing matters more than signups: a false PASS.

Run 3–5 intents your agent would actually sign — or has signed before. Send the intent (redacted is fine), the verdict, and the reasons. The number that does not exist yet is the false-PASS rate: a transaction the gate passed that you believe is unsafe.

The gate is free and rate-limited. The five paid measurements — /verify, /safety, /route, /quotecheck, /depth — are separate and not needed for this test.

What the gate does not do

It catches measurable signing mistakes: wrong network, non-canonical asset, amount over your cap, payee outside your allowlist, unlimited approval, a reverting call, a stale block. It does not screen reputation or sanctions, audit a contract, simulate full balance deltas, audit the counterparty, scan tool output for hidden instructions, or guarantee a token is safe. Those limits are printed in notChecked on every response. A PASS means the checks in checked passed at that block — nothing more.

Keep going

The gate is the front door.

Read the full request shape, the receipt contract and the five paid measurements — or run the gate against your own wallet.