Validating a Payment Request Before Paying It
Before paying a 402 challenge, an agent should verify the asset and chain, the amount against an expected range, the recipient against what it knows about that service, and a nonce or expiry that prevents replay. The challenge is untrusted input, because it comes from the party being paid.
What the challenge actually is
In a pay-per-call flow the server responds to an unpaid request with a status indicating payment is required, plus the terms: which asset, on which chain, how much, to which address, and some identifier tying the payment to this request. The client pays and retries with proof.
The important property is who wrote those terms. They come from the server, which is the party receiving the money. That makes the challenge counterparty-supplied input, in the same category as any other untrusted response body, and it deserves the same treatment: validate before acting.
This is easy to lose sight of because the flow feels like protocol rather than data. The status code is standard and the handling is mechanical, so the terms inside it inherit an unearned air of authority. But nothing about receiving a well-formed challenge says the amount is the usual amount or that the address belongs to who you think it does.
For a human-in-the-loop purchase, a person provides a sanity check on price and destination. An autonomous agent has removed that check by design, which is the point of the architecture and also the reason validation has to be explicit.
The compromised-endpoint case
The case that makes this concrete is a service you have paid many times whose challenge changes. A compromised deployment, a hijacked domain, a supply chain issue in the server's own stack, or simply an operator deciding to charge differently. Nothing in the client's key handling or spending policy detects any of that. The request still returns a valid challenge and the agent still pays it, only now the terms are different. Validation against what you expected for that service is the only layer positioned to notice.
What to check, in order
Asset and chain. Confirm the token contract address and chain ID, not the symbol. A symbol is a label anyone can reuse, and a token that displays as USDC is not necessarily the USDC contract. On Base the native USDC contract is 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, with chain ID 8453, and Base Sepolia uses 0x036CbD53842c5426634e7929541eC2318f3dCF7e with chain ID 84532. Compare full strings against a pinned list.
Amount against expectation. A spending cap answers whether you can afford it, not whether it is right. Keep an expected price or range per service and treat a departure as a policy event rather than a routine payment. A tenfold increase inside a generous per-transaction limit passes every budget check you have.
Recipient against service identity. The address should match what you already associate with that service, from a prior verified payment, a signed manifest, or configuration. Accepting whatever address the response supplies means the recipient is chosen by the counterparty at the moment of payment.
Freshness and uniqueness. A nonce or request identifier plus an expiry, checked to be unused and unexpired, so a captured challenge cannot be replayed. Without this an intercepted or repeated challenge can induce a second payment for one delivery.
Binding to the request. The challenge should refer to the specific request being paid for. A payment proof that is not tied to a request can be presented for a different one.
Transport. Verified transport security before any of the above, because a challenge obtained over a connection you cannot authenticate tells you nothing regardless of its contents.
The response after payment. Confirm you received what you paid for. Payment succeeding and delivery succeeding are separate events, and only tracking the first produces silent loss.
Where the check belongs
Outside the agent's reasoning. Validation should sit in the payment path as code, not as an instruction the model is asked to follow. A model can be argued out of a guideline by content in the response it is reading; a function that compares an address to an allowlist cannot. This is the same reason spending limits are enforced by the key's scope rather than by the prompt.
Before signing, not after. Once signed and broadcast, a payment is not recoverable by you. Every check has to complete before the signature, which means the validation path cannot depend on anything that happens after submission.
Fail closed, and make failure cheap. A challenge failing validation should stop the payment and surface for review rather than falling back to paying anyway. That is only sustainable if review is quick and rare, which argues for expected ranges wide enough to avoid noise and narrow enough to catch real changes.
Record what you validated against. Store the expected asset, price range, and recipient used for each decision, with the challenge received. When a service changes its terms legitimately you need to see what changed, and when it changes illegitimately you need the same record.
Keep first payments special. The first payment to a service has no history to validate against, so it is the one that should carry explicit approval or a deliberately small amount. Afterwards you have a baseline, and the baseline is what makes automated validation possible.
Separate the policy layer from the wallet. The thing that decides whether to pay and the thing that holds the ability to pay should be distinct, so that a bug in the first cannot quietly become unlimited authority.
Frequently asked questions
- Is an HTTP 402 payment challenge trustworthy?
- No. It is supplied by the server that will receive the payment, which makes it counterparty-controlled input. A well-formed challenge is not evidence that the amount is usual or that the recipient address belongs to the service you intended to pay, so it should be validated before an agent signs anything.
- What should an agent check before paying?
- The token contract address and chain ID rather than the symbol, the amount against an expected range for that service, the recipient against an address already associated with it, and a nonce with an expiry so the challenge cannot be replayed. Then confirm delivery after payment.
- Do spending limits make payment validation unnecessary?
- No. A limit answers whether a payment is affordable, not whether it is correct. A charge ten times the normal price can sit comfortably inside a per-transaction cap, and a payment to the wrong recipient is fully authorised as long as the amount is within scope.
- Why check the token contract instead of the symbol?
- Because a symbol is a display label that anyone can reuse when deploying a token. A contract that presents itself as USDC is not necessarily Circle's USDC. Comparing the full contract address and chain ID against a pinned list is the only check that distinguishes them.
- Should validation logic live in the agent's prompt?
- No. Put it in the payment path as code. A model reading a server's response can be influenced by that response, so instructions to be careful are not a control. A function comparing an address against an allowlist cannot be argued with, which is what makes it a boundary.
