DEV Community

Skavio
Skavio

Posted on AI-assisted

Before an AI agent pays: validate an x402 quote on Base

An AI agent that can buy external tools needs one capability before anything else: the ability to refuse a payment it didn't authorize.

An HTTP 402 Payment Required response can tell the agent what a server accepts. But HTTP 402 is not a receipt. You can inspect the payment requirements for free and decide whether to reject them before connecting a wallet.

This tutorial uses a production x402 v2 endpoint on Base to demonstrate a reproducible, wallet-free preflight. You do not need a login, API key, USDC, or payment.

Disclosure: I work on Skavio LLC. This tutorial uses our own public service and open-source examples. It is not an independent review, and the funded payment branch has not been tested.

1. Request the tool without paying

The following URL checks a public sample transaction hash. It is not a wallet key or secret:

curl -i --max-time 15 \
  'https://getskavio.com/x402/v1/usdc/verify?tx_hash=0x98809948204426c574b6f0aabdec03baa996b5c43ce28cc9ca704fa5d07e592e&network=base'
~~~{% endraw %}

The unsigned production request returns **HTTP 402** with a PAYMENT-REQUIRED header. In x402 v2, the header is base64-encoded JSON describing one or more accepted payment offers.

Read that header, not the sales-page price. In our live check on November 6, 2026, the offer was **0.02 USDC** on Base. Production prices and offers can change; a buyer should verify the response on *each* call rather than rely on an old quote.

## 2. Validate the complete offer

A per-request price limit is useful, but it doesn't protect you if a call sends the right amount to the wrong recipient or on the wrong chain.

For this *specific* example, the expected values are:

| Field | Expected value |
|---|---|
| Protocol version | 2 |
| Scheme | exact |
| Network | eip155:8453 (Base mainnet) |
| Asset | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (native Circle USDC on Base) |
| Receiver | 0x62BB64E9Ab0E8104dEf0D7845223580319d4C139 |
| Maximum amount | 20,000 base units = 0.02 USDC |

USDC on Base uses six decimals, so 20,000 divided by 1,000,000 is 0.02.

Below is the core validator from our Node.js example. The important property is **fail closed**: if the scheme, chain, asset, address, amount or number of matching offers changes, the program refuses the request.{% raw %}

~~~javascript
function inspect402(encoded, ceiling = 20_000n) {
  if (!encoded) throw new Error("Missing PAYMENT-REQUIRED header");

  const requirements = JSON.parse(
    Buffer.from(encoded, "base64").toString("utf8")
  );
  if (requirements.x402Version !== 2 ||
      !Array.isArray(requirements.accepts)) {
    throw new Error("Unsupported payment protocol");
  }

  const offers = requirements.accepts.filter(p =>
    p.scheme === "exact" &&
    p.network === "eip155:8453" &&
    String(p.asset || "").toLowerCase() ===
      "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" &&
    String(p.payTo || "").toLowerCase() ===
      "0x62bb64e9ab0e8104def0d7845223580319d4c139" &&
    /^[0-9]+$/.test(String(p.amount))
  );

  if (offers.length !== 1) throw new Error("Ambiguous payment terms");
  const amount = BigInt(offers[0].amount);
  if (amount <= 0n || amount > ceiling) {
    throw new Error("Price exceeds authorized cap");
  }
  return { amount, offer: offers[0] };
}
~~~

This is a policy example, not a universal x402 validator. Your agent must load the correct merchant and allowed token for *each service* from an independently trusted configuration.

## 3. Run a real free quote preview

The full scripts, error handling and unit tests are publicly available:

[**Skavio x402 safe buyer: Python and Node.js**](https://github.com/skavio-eu/skavio-agent-tools/tree/main/examples/x402-safe-buyer).

To try the unpaid mode:

~~~bash
git clone https://github.com/skavio-eu/skavio-agent-tools.git
cd skavio-agent-tools/examples/x402-safe-buyer

python3 preview_x402.py
node skavio-x402.mjs --preview
node --test
~~~

These commands **do not provide wallet credentials or authorize payment**. For this endpoint the preview output looked like:

~~~json
{
  "status": 402,
  "network": "eip155:8453",
  "scheme": "exact",
  "priceUSDC": 0.02,
  "paid": false
}
~~~

The word to notice is **false**. A price challenge is not a successful purchase.

We verified the real unsigned HTTP 402 with both Python and Node.js. On November 6 our Node test suite passed **3/3**, exercising budget parsing, rejecting invalid or ambiguous payment details and confirming that preview mode does not sign anything. That is meaningful evidence for the **free preflight**, not proof of a funded mainnet transfer.

## 4. Why a single-call cap is not enough

The production risk begins when an agent loops.

If the cap is 0.02 USDC, 1,000 permitted calls could still spend **20 USDC**. A paid implementation also needs:

- A named business task and explicit authorization to buy the tool.
- A cumulative spend budget per task, per day and per wallet.
- A dedicated low-balance wallet, never the company treasury.
- Safe handling of timeouts, idempotent request identities and ambiguous retries.
- A settlement receipt that matches the intended network, token, receiver and amount.
- A manual stop condition and logs suitable for reconciliation.

The GitHub example includes an **explicit opt-in** paid mode backed by the official x402 client libraries. We have verified that the libraries import, but we have **not** executed a funded mainnet call. Do not treat the optional branch as independently settlement-tested.

## 5. Record quote, completion and settlement separately

A robust buyer should distinguish:

| Stage | Evidence | What it does NOT prove |
|---|---|---|
| Quoted | HTTP 402 with a validated offer | No payment occurred |
| Tool completed | Successful endpoint response | Payment settlement might still need verification |
| Paid and verified | Matching receipt and settlement evidence | The returned data still needs validation |

This is especially important if your network times out *after* a wallet tries to pay. Blindly repeating the whole operation with a new ID can create duplicate spending. Store and reconcile the prior attempt first.

## A useful five-minute exercise

Run the free script with the expected network deliberately changed from Base to Ethereum mainnet. It should reject the offer. Repeat with an incorrect receiver or a ceiling below 0.02 USDC. If it *still* proceeds, it isn't enforcing your purchasing policy.

**Takeaway:** A useful buying agent isn't defined by the ability to pay. It is defined by the ability to **refuse, explain and later reconcile** a purchase.

---

**Reproducible code:** [Python/Node x402 buyer with tests](https://github.com/skavio-eu/skavio-agent-tools/tree/main/examples/x402-safe-buyer)  
**API specification:** [Skavio x402 docs](https://getskavio.com/x402/docs/)  
**Optional no-signup document examples:** [Skavio demo gallery](https://getskavio.com/demo/)

*Those resources are maintained by Skavio LLC and supplied so readers can reproduce the exercise. They are not third-party endorsements.*
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
book0feli profile image
Elijah •

Good read. On the point that agents must validate payment terms before paying to refuse unauthorized transactions: spending limits stop an agent from overspending, but they don't stop it paying a broken or hijacked endpoint inside the limit. A per-payment check (is this endpoint, price and wallet what it should be?) covers that gap.

(I work on Lumière PayCheck, which checks x402 endpoints before agents pay them.)