Skip to content

561 Group · Engineering

Never trust the payee in a 402 challenge: buyer-side x402 payee verification

An x402 buyer must take the payee from its own authority, not the seller's 402 challenge. How an expectedPayTo check, a budget and quote-drift refusal work.

Engineering

Never trust the payee in a 402 challenge: buyer-side x402 payee verification

An x402 buyer should never spend to whatever address the seller's 402 challenge names. The challenge is written by the party asking for money, so the buyer's owner must supply the expected payee, network, asset and ceiling independently, before the buyer probes or signs, and the buyer must refuse any challenge that deviates. The Red Cup buyer library enforces this: it requires an independently authorized expectedPayTo for every paid request and refuses a challenge naming a different recipient before a signature exists.

What is the attack?

An autonomous agent that pays for APIs is a wallet attached to a language model and an HTTP client. Consider an agent told "buy the weather data from this URL". If its x402 client signs whatever the response asks for, then any party that controls the response can set payTo to its own address, raise amount, or swap asset. The attacker needs no cryptographic break. It needs one of: a compromised seller, a man-in-the-middle on a bad network path, an open redirect, a typo-squatted domain, or a prompt injection that sends the agent to a hostile URL. The payment protocol will faithfully carry a valid signature to the wrong place.

The payTo in a real challenge is one field in a JSON document the seller controls. It is a claim. Nothing in the protocol makes the buyer believe it.

What does the Red Cup buyer require before it signs?

The buyer package, x402-exact-purchase-service (MIT, public at github.com/red-cup-engineering/x402-exact-purchase-service), states the rule in its documentation:

For every paid request, the owner supplies an independently authorized expectedPayTo before any network probe or signing. Unknown payees may be observed with the unsigned quote API only; a seller-authored 402 challenge must never select its own recipient as the buyer's spending authority. A challenge naming a different recipient is refused before signing.

The owner of the agent, not the agent, supplies four things:

InputPurpose
networkThe CAIP-2 chain, e.g. eip155:8453 for Base
assetThe token contract, e.g. USDC on Base
maximumAmountA ceiling in atomic units (a string, never a float)
expectedPayToThe recipient the owner approved, obtained out of band

The unsigned quote function then sends exactly one probe and admits only a payment requirement that matches all four:

js
import { readHttpPurchaseQuote } from "@red-cup-engineering/x402-exact-purchase-service/http-quote";

const quote = await readHttpPurchaseQuote({
  url: "https://merchant.example/compute",
  method: "POST",
  body: { size: 3 },
  network: "eip155:8453",
  asset: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
  maximumAmount: "10000",              // atomic units, not a dollar float
  expectedPayTo: "0xYourApprovedPayee", // from your own authority, not the 402
  fetchImpl: enterpriseEgressSafeFetch, // supplied by the operator
});
// quote.requirement and quote.request are inspectable; no signer was opened.

(Adapted from the package README; the payee here is a placeholder.) Three details deserve emphasis.

The fetch is supplied, not ambient. The caller passes an egress-safe fetchImpl. The library does not fall back to globalThis.fetch, so a signed authorization cannot be sent to an unadmitted endpoint by default. Redirects are not followed.

A quote is not a purchase. The quote function reports explicitly that no signing, payment, settlement or delivery has occurred. It sends a single unsigned probe, and the package warns that probing with POST is appropriate only for sellers known to be payment-first, since an untrusted non-x402 origin might perform a side effect before returning 402.

Quote drift is refused. When the owner passes an accepted quote into the purchase, the library binds the URL, HTTP method, a digest of the caller's headers and a digest of the serialized body. Before signing, it requires the new 402 to preserve the entire accepted payment requirement: the exact amount, recipient, timeout and token-signing metadata. Request drift is refused before any egress; terms drift is refused before a signature with the error code X402_QUOTE_DRIFT. This closes the gap between "I looked at the quote" and "I signed something else".

Can a facilitator's report override this?

No. The package notes that optional recipient, asset or payer fields in a facilitator report cannot contradict the admitted terms, and that a facilitator report is still not independent chain settlement proof. It keeps three observations separate: a settlement report, received bytes, and the requester's acceptance. The buyer exposes beforePayment, which receives the exact request and signed payment before transmission, and onSettlement, which receives the facilitator's report before the delivery is read. The purchasing enterprise is expected to write both to its own books. If custody of the intent fails before payment, the purchase must stop before the signature crosses the network. If custody fails after settlement, the record must say the charge may have happened.

How does the seller practise the same rule on itself?

The seller side follows the discipline when it checks its own deployment. The market repository's receiving script (ops/receive-deployment.mjs) reads X402_EXPECTED_PAY_TO and refuses to run if "independently authorized X402_EXPECTED_PAY_TO is missing or malformed". The operator procedure for the directory-seeding script says the signed payment's recipient must match the independently approved value, and "do not derive the recipient from a source-code literal, the 402 challenge, or the seller's own X402_PAY_TO variable". A missing or malformed authority refuses before any live discovery fetch, signing attempt or journal creation. The listing reconciler excludes any offer whose advertised payee, asset, network or price differs from the deployed catalog, because "interpreting any HTTP 402 as a valid merchant offer" is exactly the mistake this article warns about.

The same pattern applies to chains of purchases. The buyer's service-chain executor runs two to eight separately payable POST cells, but only after validating each cell's origin and independent payee and requiring that the sum of the per-cell maximum charges fits inside the owner's total cap. The seller's own 402 is never approval to spend.

How can I observe a challenge without paying at all?

If you only need to look, use the observation helper in the Apache-2.0 x402 boundary reference. Its observePublicX402Resource function takes an HTTPS URL and an egress-safe fetch, refuses localhost, .local, credential-bearing URLs and literal IP hosts, refuses redirects, requires exactly status 402 and a bounded payment-required header, validates the challenge with the official x402 schema, lower-cases addresses, and returns an immutable record with a content-addressed digest of the normalized challenge. It does not sign, pay, settle or consume the protected response. It leaves DNS, SSRF and egress policy to the hiring enterprise, which is the right place for them.

What checklist should an agent operator follow?

  1. Obtain the seller's payee from a channel the seller's HTTP response cannot influence: a signed registry, your own configuration, a published domain-bound record you verify separately.
  2. Configure network, asset and a per-call and total ceiling in atomic units.
  3. Probe once, unsigned. Compare every field of the challenge to your configuration.
  4. Pin the accepted terms and refuse drift between probe and signature.
  5. Persist the intent before the signature leaves the process; persist the settlement report before reading the delivery.
  6. Treat the facilitator's report, the received bytes and the acceptance as separate facts.
  7. Never let a model decide the payee.

Seller-side policies that complement this are in what an x402 seller should do beyond the spec, including how a retried purchase returns the same bytes.

Contents

Explore

Tagged

  • x402
  • agent-security
  • payee-verification
  • http-402
  • wallets