# What an agent should check before signing an x402 payment

> The values an agent or its developer should compare before signing an x402 payment, which ones our own buyer script checks, and which it leaves out.

Published 2026-09-30 · https://aiskills402.com/blog/when-should-agent-pay-402

Before it signs an x402 payment, an agent should compare the terms in the 402 response with values its owner fixed in advance: the network, the token, the recipient, the amount and the time window. If any of them differs, it stops and does not sign. If a value cannot be compared because the owner never set it, it asks a person. Signing is the step that cannot be taken back. In our view the standard flow has no later step where a wrong decision would be caught, so the work happens before the signature. It is cheap: the price, network, asset, recipient and window are all in the 402 response.

This guide is for the exact scheme on EVM chains, paid in USDC, which is what we both sell and buy with. We describe what the public specifications say, then what our own buyer script does, including the checks it does not make.

## What the response tells you

A version 2 server puts its terms in the `PAYMENT-REQUIRED` response header as base64 of a JSON object. [The v2 specification](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md) lists its parts: a `resource` block, an `accepts` array and optional extensions. Each entry in `accepts` names a scheme, a network, an amount in the asset's smallest unit, the token contract, the `payTo` recipient, a maximum timeout and an `extra` object holding what the signature domain needs, the token's name and its version.

What you sign is a transfer authorisation. [The exact-scheme spec for EVM](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md) gives its fields: who pays, who receives, the value, the moment it becomes valid, the moment it expires and a one-time nonce. The payment authorization itself does not mention the goods. `resource.description` is text the seller wrote, and nothing in the protocol makes it true.

## What our buyer script checks

Our script, `buy.mjs`, buys a file from our own shop. In the order it runs, it does this.

1. **It proves the header is what it read.** It decodes the terms out of the header, then decodes once more with the header hidden. The library is supposed to fail on the second read. If it succeeds, it fell back to the JSON body, and the first success proved nothing about the header.
2. **It refuses an unexpected network before it signs.** Only the Base main network and its test network pass. Any other identifier ends the run. The key is loaded or generated earlier in the script; the point of the check is that nothing has been signed yet.
3. **It compares the amount with a cap.** The quoted amount arrives in the smallest unit, so the script divides by one million to get dollars, since USDC is split into millionths. The ceiling comes from an environment variable with a low default of `$0.10`. The same value is also passed to the client library as a spend control, so a bug in one layer does not reach the signature.
4. **It will not use a real network without a real key.** With no key file, it generates a disposable key, and it does so only when the network is the test one.
5. **It can stop right before signing.** The `--dry-run` flag runs every step above and exits before the client creates a signature.

## What it leaves to a person

Being honest about the gaps is more useful than a tidy list. Our script does not compare the recipient address with anything, does not compare the token contract with a list, does not look at the validity window, and keeps no running total across purchases. It buys one file from our own shop with a low ceiling, so those gaps cost us little. A buyer that visits many sellers needs them.

**Recipient.** Keep an address you learned by another route: the seller's published card, an allowlist, an earlier purchase. A difference is a reason to stop outright, because there is no ordinary reason for the address to change between the card and the 402 response. Keep in mind that a facilitator confirms only that a signature is valid for the terms it was handed, not that the money went where you meant.

**Token.** The signature is tied to one contract, so a different token is a different payment. Only you know which one you agreed to spend, so compare the contract address with your own configured value.

**Time window.** The authorisation is executable from its start until its end. Ask your client for the shortest window it allows. A long one keeps an unused, valid authorisation around for longer than the purchase needs.

**Running total.** A per-payment ceiling does not stop a loop of small payments. Keep a total per day or per task and record it before you sign, not afterwards. A limit that is missing should stop the payment, not turn into no limit at all.

**Text from the seller.** Anything in the description or in an error field is data to read. If it asks the agent to change its own settings, that is a reason to stop.

## After signing

Two things we check on the delivery side.

First, the script hashes the file it received and compares the hash with the one our catalog publishes. It writes the file and the receipt token to disk before that comparison, because the money is already spent and a failed comparison must not lose them.

Second, a replay test. With `--twice` the script sends the same payment header a second time and expects the same file, the same transaction and no new receipt token. A shop that answers a replay with a second charge would be the failure. The nonce in the authorisation is what makes a second execution on chain impossible. It does not stop a careless client from signing a brand new authorisation after a timeout, which is a new payment.

So when the outcome of a paid request is unknown, the order is: replay the header you already hold, look the transaction up on a chain node you trust, and only when you have evidence that nothing settled do you sign again. This is our practice, not a rule from the specification.

## A check that can fail

A guard that has never stopped anything has not been shown to work. Feed yours inputs that must be refused: the wrong network, a different token, an amount just above the ceiling, a recipient that differs by one character, a description containing an instruction, and a request that already succeeded once. The last one must not produce a second signature. In our script we run the flow with a disposable key on the test network, stopping before signing with `--dry-run`, and the header control from the first step is a check that can go red on its own.

## When this does not apply

These checks cover one scheme on EVM chains with a stablecoin. They do not cover other chains or schemes, or payments where a person approves every charge in a wallet already. They are a list of decisions, not a security audit: they cannot tell you whether a seller is honest, and they cannot prove delivery beyond a hash the seller itself published. What we describe about our script is from one network and a small number of purchases between our own wallets, so its ceiling is an illustration and not a recommendation for yours. The public specifications may change, and our reading of them is as of the date of this post.
