Blog, by , , 8 min read

x402 payment challenge: 7 things to check before you pay

What an x402 402 response contains, how to decode the PAYMENT-REQUIRED header, and seven checks an agent should run on it before signing a payment.

8min read
On this page
  1. What a challenge contains
  2. 1. The version
  3. 2. The resource URL
  4. 3. The scheme and the network
  5. 4. The asset
  6. 5. The amount
  7. 6. The pay-to address
  8. 7. The timeout and the extensions
  9. After you pay
  10. Automating the checks
  11. Frequently asked questions

Short answer: an x402 server answers an unpaid request with HTTP 402 and a base64-encoded JSON object, in the PAYMENT-REQUIRED header under x402 v2 or in the response body under v1. Decode it before your agent signs anything and check the version, the resource URL, the network, the asset, the amount, the pay-to address and the timeout. Most of these only tell you something when compared with what you expected: as of 16 October 2026, ToolVet's checks flag 1,257 listings for charging more than their listed price and 973 for a recently changed payment address, and neither is visible from a single challenge on its own.

Some background: x402 lets a client, usually an AI agent, pay for an HTTP request in stablecoins. The client asks, the server says what it wants, the client signs a payment and asks again. The whole negotiation is one object, the payment challenge, and the client SDK normally reads it, picks a payment option and signs in one step. That is the step where an agent commits money, so it is worth knowing what is in that object and what can go wrong with each field.

What a challenge contains

The x402 v2 specification defines the PaymentRequired object. The definitions below are quoted from it, except where marked:

Field Definition
x402Version "Protocol version identifier (must be 2)"
error "Human-readable error message explaining why payment is required"
resource.url "URL of the protected resource"
resource.description "Human-readable description of the resource" (optional)
resource.mimeType "MIME type of the expected response" (optional)
accepts[] "Array of payment requirement objects defining acceptable payment methods"
accepts[].scheme "Payment scheme identifier", with exact as the example
accepts[].network "Blockchain network identifier in CAIP-2 format", with eip155:84532 as the example
accepts[].amount "Required payment amount in atomic token units"
accepts[].asset "Token contract address or ISO 4217 currency code for fiat"
accepts[].payTo "Recipient wallet address or role constant", with merchant as the example
accepts[].maxTimeoutSeconds "Maximum time allowed for payment completion"
accepts[].extra "Scheme-specific additional information"
extensions "Protocol extensions data": a map from extension id to info and schema (summarised)

In v2 the whole object travels in the PAYMENT-REQUIRED response header, base64-encoded, and the HTTP transport spec says the body is "a server implementation concern". To read it by hand:

curl -s -D - -o /dev/null https://api.example.com/paid-endpoint \
  | grep -i '^payment-required:' | cut -d' ' -f2 | tr -d '\r' \
  | base64 -d | jq .

If there is no such header but the body is JSON with x402Version: 1, you are looking at a v1 challenge: same idea, but the amount field is maxAmountRequired, the resource URL sits inside each accepts entry, and networks have names like base-sepolia instead of CAIP-2 ids. The reference SDKs stay compatible with v1, so a client doesn't need to care which one it got. A person reading the challenge does.

1. The version

x402Version must be 2 for a v2 challenge. A server that still sends v1 works with today's SDKs, but it is also a server that still speaks the protocol from before the v2 launch in December 2025, which introduced the extensions mechanism and the discovery metadata that fills the catalogs. The free x402 checker reports which version a challenge uses.

2. The resource URL

resource.url should be the https:// URL you called. Two things go wrong here. A server behind a TLS-terminating proxy often advertises http://, because its middleware builds the URL from the request it sees; that endpoint still takes payments, but Coinbase's catalog won't index it, as we covered in why an endpoint isn't in the Bazaar. And a URL that differs from the one you called, a different host or path, means either a misconfigured server or a challenge copied from somewhere else. Either way, the payment payload echoes this resource object back to the server, so it should be the one you want.

3. The scheme and the network

scheme names the payment method. The v2 spec currently defines exact: the buyer signs a transfer of a fixed amount straight to the pay-to address. network is a CAIP-2 id of the form namespace:reference, where the reference on EVM chains is the chain id: eip155:8453 is Base mainnet and eip155:84532 is Base Sepolia, a testnet, per Base's network information. The reference client only pays on networks you registered a scheme for; the buyer quickstart shows client.register("eip155:*", ...), and if nothing matches it throws "No scheme is registered for the required network". Before widening the registration, check whether the endpoint is asking for a chain you meant to pay on. A challenge that lists several accepts entries lets the client choose; make sure the choice is yours, not the first entry's.

4. The asset

asset is the token's contract address on that network, or a currency code for fiat. A challenge can name any token. The reference client's default spend controls only pay "recognized USD-pegged assets (e.g. USDC)", and ToolVet's security scan flags a listing that asks for a token it doesn't recognise. If you set allowedAssets yourself, pair each asset with its network: the same symbol has a different contract address on every chain.

5. The amount

amount is a string in the token's smallest unit, not in dollars. With USDC's six decimals, "10000" is 0.01 USDC (Avalanche's x402 course spells this out; the x402 spec calls them "atomic token units"). Two checks:

  • Against your cap. The reference client caps each payment at $1 by default, maxAmountPerPayment in TypeScript, max_amount_per_payment in Python. Keep it on, and set it near the prices you expect to pay. The quickstart says spend controls "run before any custom policies and before the payment payload is signed".
  • Against the advertised price. A challenge can ask for more than a listing or a catalog says. ToolVet compares the live amount with the price it has on record and takes the honest-pricing points away when the live price is more than 10% above the advertised one; 1,257 listings carry that flag as of 16 October 2026, 20:55 UTC (stats). A price change isn't fraud, but an agent that pays whatever the challenge says has no price at all.

6. The pay-to address

payTo is where the money goes, and with the exact scheme it goes there directly: the facilitator verifies and submits the transfer and never holds the funds, as the facilitator comparison explains. So this field is the one a compromised or hijacked server would change. From a single challenge you can't tell; from history you can. ToolVet records the pay-to address on every check and flags a listing when it changes; 973 listings had a recently changed payment address as of the same timestamp. A change can be the owner rotating a wallet; it can also be a hijacked server. All of them are worth a pause, which is why every listing's status page at /status/<listing> announces pay-to changes in its Atom feed, next to outages and price changes, and the free GET /api/v1/watch?ids= endpoint returns them for up to 25 listings at once.

7. The timeout and the extensions

maxTimeoutSeconds is how long the server gives the payment to complete; the spec's examples use 60. A very short timeout on a slow network means a failed call: with exact, money moves only when the facilitator settles, so nothing is lost, but nothing is delivered either.

extensions is new in v2. Servers advertise extensions here, the Bazaar discovery metadata among them, and clients echo them back in the payment payload: the client "must include at least the info received" and may append but not delete. A client that strips extensions is out of spec, whatever the facilitator then does with the payment.

After you pay

The retried request carries the signed payment in PAYMENT-SIGNATURE, and the response carries PAYMENT-RESPONSE, again base64 JSON: success, transaction (the on-chain hash, empty on failure), network, payer and, on failure, errorReason. The transport spec maps outcomes to status codes: 200 for verified and settled, 402 for a failed verification or settlement, 400 for a malformed payload, 500 for a server error. Log the decoded PAYMENT-RESPONSE with every paid call; it is the receipt, and the transaction hash is how you verify it on-chain.

Automating the checks

A person can decode one challenge. An agent calling a hundred endpoints a day needs the comparison done for it, which is what a preflight check is: send the unpaid request, decode the challenge, run the checks above, and compare amount and pay-to with history.

  • The free x402 checker does this for any URL in the browser, grades the challenge from A to F and never pays.
  • The preflight_check tool on the ToolVet MCP server, or GET /api/v1/check?url=, costs $0.005 per call over x402 and returns a verdict of ok, caution or avoid with the reasons, the offered payment options, the HTTP status and the latency. caution also covers endpoints ToolVet hasn't listed yet, because then there is no history to compare against.
  • get_service_status is free and says whether a listed endpoint is up right now.

Frequently asked questions

Where is the price in an x402 402 response?

In x402 v2 it is in the PAYMENT-REQUIRED response header, a base64-encoded JSON object whose accepts array lists one or more payment options, each with an amount in the token's smallest unit, an asset, a network and a payTo address. In v1 the same information is in the response body, with the amount named maxAmountRequired.

Why does my x402 client say "No scheme is registered for the required network"?

The challenge asks for a network, in CAIP-2 form such as eip155:8453, that your client has no payment scheme registered for. Decode the challenge and look at accepts[].network before you register more: the endpoint may be asking for a testnet or a chain you don't hold funds on.

Can an x402 endpoint charge more than it advertises?

Yes. The challenge is the only binding price, and a listing or catalog entry can lag behind it. ToolVet compares every check with the price on record and flags listings whose live price is more than 10% above the advertised one; as of 16 October 2026 that applies to 1,257 listings. A per-payment cap in your client is the safeguard that doesn't depend on anyone's listing being current.

How do I check an x402 URL without paying?

Send the request without a payment header and decode the 402. The free x402 checker does that and grades the result; the preflight_check MCP tool does the same for agents and adds a comparison with the price and pay-to address ToolVet has seen before. Neither ever pays the endpoint.

How we check and score explains what ToolVet records on each check and how the flags above feed the trust score.