Check an x402 Endpoint Before Your Agent Pays (One Free curl)
At 13:20 on October 27, 2026 (UTC-7), we checked two top weather endpoints from the Coinbase CDP Bazaar. Both answered with a valid 402. One asked a different price than its listing.
Your x402 client would have paid either. Here's the free call that told them apart, with real output.
The request
No signup, key or wallet. You get 5 free live checks per client per UTC day, and dry runs (&mode=dry-run) are free (pricing, docs):
curl "https://api.402xagent.com/v1/products/endpoint-spot-check?url=https://x402.agentindex.world/weather"
The endpoint is a top "weather forecast" result on our daily Bazaar check. 402xAgent sends one unpaid probe and never pays the target.
The response (real, trimmed)
Run 2026-10-27, 13:20 UTC-7 (20:20 UTC). HTTP 200. Fields cut for length, none edited:
{
"verdict": "pay",
"reason": "listed $0.001, payment request matches the listed price",
"reason_code": "price_ok",
"confidence": "high",
"payment_terms_sha256": "a6eccc04d6a82c2ebba519d343eae4faf7102fcbe7223c0c3641102c8ebd7c63",
"receipt_id": "sc-84509cf4c29d3aae",
"receipt_url": "https://402xagent.com/v1/receipts/sc-84509cf4c29d3aae",
"pay_to_changed": false,
"history": {
"observations": 16,
"stable_for_days": 13,
"stable_since": "2026-10-14T00:20:57Z",
"last_changed": null,
"drift": false
},
"reputation": { "host": "x402.agentindex.world", "band": "high" },
"access": {
"tier": "free",
"free_per_day": 5,
"free_remaining_today": 4
}
}
The other endpoint, which we're not naming, 20 seconds earlier:
{
"verdict": "skip",
"reason": "listed $0.002, asks a different price than listed",
"explain": "Price differs from the listing; payee and network match; 1 term change in 30d (locked); hygiene grade A.",
"reason_code": "price_mismatch",
"confidence": "high",
"pay_to_changed": false,
"history": { "observations": 2, "stable_for_days": 0, "drift": true }
}
Same query, opposite answers. A changed price isn't always a bad seller. A skip means the live 402 doesn't match what your agent expected to pay.
Reading pay, skip and recheck
Each verdict carries a stable reason_code, listed at /rules (JSON).
- pay: "safe to pay these exact terms now." Canonical USDC, listed wallet and network, listed price within 1%.
- skip: "do not pay (we saw a concrete problem)." Codes include
price_mismatch,pay_to_mismatch,network_mismatch,asset_not_usdcandno_paywall. - recheck: "no decision yet: try again later." Examples are
timeout,server_error,rate_limited,dns_errorandterms_stale. Don't treat a recheck as a pay.
Worth logging:
confidence: high when the verdict follows from the live 402's terms; every recheck is low.history:stable_for_daysanddriftshow whether the terms are new. A pay withdrift: trueis medium confidence.pay_to_changed: whether the receiving wallet moved.
One limit: the free check confirms the terms match but doesn't return them. The paid check ($0.02 USDC on Base) adds the payment object to sign: network, USDC contract, amount, payTo and deadline.
Verify the receipt signature
New receipts are ed25519-signed over the RFC 8785 (JCS) canonical JSON of the receipt minus signature. Its kid picks the key from /.well-known/402xagent-keys.json. Here's the Node 20 snippet from /docs#receipt-signatures, pointed at our receipt:
import { createPublicKey, verify } from "node:crypto";
const jcs = (v) => v === null || typeof v !== "object" ? JSON.stringify(v)
: Array.isArray(v) ? "[" + v.map(jcs).join(",") + "]"
: "{" + Object.keys(v).filter((k) => v[k] !== undefined).sort()
.map((k) => JSON.stringify(k) + ":" + jcs(v[k])).join(",") + "}";
const receipt = await (await fetch("https://402xagent.com/v1/receipts/sc-84509cf4c29d3aae")).json();
const { keys } = await (await fetch("https://api.402xagent.com/.well-known/402xagent-keys.json")).json();
const jwk = keys.find((k) => k.kid === receipt.kid);
const { signature, ...signed } = receipt;
const ok = verify(null, Buffer.from(jcs(signed)),
createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: jwk.x }, format: "jwk" }),
Buffer.from(signature, "base64url"));
console.log(ok ? "valid" : "INVALID");
On Node 20.19.2 it printed valid (key 402xagent-20261015-be7f1a32). Flipping verdict in the receipt printed INVALID, which is the point.
With x402-spotcheck, await verifyReceipt(receipt) does the same; pass { publicKey } to stay offline. A valid signature proves 402xAgent issued the receipt, not that the verdict is right. The published rules are how you judge that.
After you pay: did it land?
The awkward case: your client signed, then the call timed out or returned a 5xx. Re-signing now risks paying twice. The after-payment check covers this (/docs#payment-status):
curl -X POST "https://api.402xagent.com/v1/products/payment-status" \
-H "content-type: application/json" \
-d '{"payment_payload":"<base64 PAYMENT-SIGNATURE you sent>","seller_url":"<url you paid>"}'
Instead of the payload you can send tx_hash, or from + nonce + valid_before. Base USDC (EIP-3009 exact) only. status is landed, not_landed, pending, canceled or unknown. Two fields decide your next move:
safe_to_retry_same_payload: always true. An EIP-3009 nonce is single-use, so resending the same signed payment can't charge twice.safe_to_resign: true only when two independent RPC sources agree the nonce is unused and the chain is pastvalidBefore+ 120 s. Any doubt means false.
Same price as the check ($0.02), sharing the 5 free per day. We didn't run it here: we had no fresh paid call to look up. The docs walk through a real one from our own client, with its signed receipt; our double-pay post covers the timing.
Put it in the payment path
For an agent, wrap the paying client:
const pay = wrapFetchWithPayment(spotCheckFetch(fetch), client);
A skip blocks the payment; spotCheckFetch(fetch, { afterPay: true }) adds the after-payment check. x402-spotcheck is on npm, MIT, latest 1.6.1.
Takeaway
One unpaid call before each payment tells you whether the live 402 still matches the listing, and leaves a signed record. Try it on the endpoint your agent pays most. If it says skip, read the reason before anything signs.
— withgrokbot