---
title: "Return HTTP 402 with an x402 payload on a paid resource"
slug: return-402-x402-for-agents
published: 2026-10-14T08:15:35.792673+00:00
updated: 2026-10-14T08:15:35.792673+00:00
author: "AIScan Content Team"
author_url: https://aiscan.site/about
category: "AI Readiness"
tags: check:M3, x402, http 402, agentic commerce, machine payments, fix guide
description: "Return a 402 with an x402 payload for a paid endpoint: required fields, a working server, a verifier script, and who should skip it. Checked 14 Oct 2026."
url: https://aiscan.site/blog/return-402-x402-for-agents
---

Most sites should not return a 402. If you sell nothing per request, skip this guide and keep your score. If you do charge for an API response, a report or a dataset, this is how to return an x402 payment requirement that an agent can read, in about 30 lines of code and one verification command. It is current as of 14 October 2026, against x402 protocol version 2.

## Quick summary

| Question | Answer | Source |
|---|---|---|
| What does AIScan check? | `check:M3`, the x402 Payment Required signal. It is optional, so a miss never lowers your score | [AIScan commerce docs](/docs/checks/commerce) |
| What must the response be? | HTTP `402` with a `PAYMENT-REQUIRED` header holding base64-encoded JSON | [x402 HTTP transport spec](https://raw.githubusercontent.com/coinbase/x402/main/specs/transports-v2/http.md) |
| Which fields are required? | `x402Version`, `resource`, and `accepts[]` with `scheme`, `network`, `amount`, `asset`, `payTo`, `maxTimeoutSeconds` | [x402 core spec](https://raw.githubusercontent.com/coinbase/x402/main/specs/x402-specification-v2.md) |
| Who settles the payment? | A facilitator service verifies and settles; your server only describes the price and checks the retry | Same spec, section 7 |
| Who should skip it? | Anyone with free content or a normal checkout. Most sites | Path 1 caveats below |

**Fastest check:** run `npx aiscan-cli yoursite.com` and read the M3 row. Then use the script in Path 1 to test the one URL you gate.

## What the 402 is for

HTTP has had a `402 Payment Required` status code for decades with no agreed meaning. x402 gives it one. The [x402 specification](https://raw.githubusercontent.com/coinbase/x402/main/specs/x402-specification-v2.md) describes a four-step loop: a client asks for a resource, the server answers 402 with its payment requirements, the client retries with a signed payment authorization, and the server verifies and settles it.

Think of a toll booth that posts its price on a sign. The agent drives up, reads the sign, pays, and drives through. The sign has to be readable by a machine, which is why it is a fixed JSON shape and not a paragraph on a pricing page.

AIScan's M3 row looks for that sign. On a site with nothing gated it reports "no paywalled resource declared (optional)" and stays informational. Its [check guidance](/docs/checks/commerce) calls the check relevant only if you monetize specific resources for agents.

## Path 1: write the 402 yourself

You need three things before any code: a wallet address that receives payment, a network and token you accept, and a price in the token's smallest unit. The spec's own example uses Base Sepolia (network `eip155:84532`, a test network) and USDC, which has six decimals, so an `amount` of `10000` is 0.01 USDC. Start on a test network.

1. Pick one URL to gate, such as `/premium/report.json`. Never gate `/`, `/robots.txt` or `/llms.txt`.
2. Build the requirements object with the fields below.
3. Base64-encode its JSON and send it in the `PAYMENT-REQUIRED` header with status 402.
4. When a request arrives with a `PAYMENT-SIGNATURE` header, hand it to a facilitator to verify and settle before you return 200.
5. Send `Cache-Control: no-store` on the 402 so a CDN does not serve one buyer's requirements to everyone.

This stdlib Python server does steps 1 to 3 and 5. The `PAYMENT-SIGNATURE` branch only checks that the header exists, which is a placeholder, not payment verification.

```python
import base64, json
from http.server import BaseHTTPRequestHandler, HTTPServer

REQ = {"x402Version": 2,
  "error": "PAYMENT-SIGNATURE header is required",
  "resource": {"url": "http://127.0.0.1:8402/premium/report.json",
               "description": "Monthly crawl report", "mimeType": "application/json"},
  "accepts": [{"scheme": "exact", "network": "eip155:84532", "amount": "10000",
    "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
    "maxTimeoutSeconds": 60, "extra": {"name": "USDC", "version": "2"}}]}

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != "/premium/report.json":
            self.send_response(404); self.end_headers(); return
        if self.headers.get("PAYMENT-SIGNATURE"):   # placeholder: verify via a facilitator
            self.send_response(200); self.send_header("Content-Type", "application/json")
            self.end_headers(); self.wfile.write(b'{"report": "paid content"}'); return
        blob = base64.b64encode(json.dumps(REQ).encode()).decode()
        self.send_response(402)
        self.send_header("Content-Type", "application/json")
        self.send_header("PAYMENT-REQUIRED", blob)
        self.send_header("Cache-Control", "no-store")
        self.end_headers(); self.wfile.write(b"{}")

HTTPServer(("127.0.0.1", 8402), H).serve_forever()
```

The `payTo` and `asset` values above are the spec's example values for Base Sepolia. Replace `payTo` with your own address. For a real network, take the token contract address from the token issuer, not from this page.

## Path 2: let a framework do it

You do not have to hand-roll the header. The [x402.org](https://www.x402.org/) site and the project's documentation are the place to look for server integrations and facilitator services, which handle the signature check and settlement that Path 1 skips. We did not install or evaluate any of them for this guide, so read the current docs before you pick one.

A decision rule: if you charge for one endpoint and can host a facilitator call, use middleware. If you only want to confirm that an agent can read your price, Path 1 plus the script below is enough.

## Verify the response

Run the scan first. `npx aiscan-cli yoursite.com` covers `check:M3` and the neighbouring commerce checks, `check:M1` and `check:M4`. The scan reports on the pages it probes, so a single gated API URL deep in your site may not show up in it. That is the reason for the second step.

If you would rather check by hand, or you want to test the one gated URL, `curl -i https://yoursite.com/premium/report.json` should show `HTTP/1.1 402` and a long `PAYMENT-REQUIRED` header. The script below goes further and decodes it.

```python
import base64, json, sys, urllib.request, urllib.error
url = sys.argv[1]
try:
    urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "x402-check/1.0"}), timeout=15)
    print("FAIL: got 2xx without payment (resource is not gated)"); sys.exit(1)
except urllib.error.HTTPError as e:
    status, headers = e.code, e.headers
problems = []
if status != 402: problems.append(f"status is {status}, expected 402")
raw = headers.get("PAYMENT-REQUIRED")
body = {}
if not raw: problems.append("no PAYMENT-REQUIRED header")
else:
    try: body = json.loads(base64.b64decode(raw))
    except Exception as exc: problems.append(f"header is not base64 JSON: {exc}")
if body:
    if body.get("x402Version") != 2: problems.append("x402Version is not 2")
    if not body.get("resource", {}).get("url"): problems.append("resource.url missing")
    if not body.get("accepts"): problems.append("accepts[] empty")
    for a in body.get("accepts", []):
        for k in ("scheme", "network", "amount", "asset", "payTo", "maxTimeoutSeconds"):
            if k not in a: problems.append(f"accepts[].{k} missing")
        if not str(a.get("amount", "")).isdigit(): problems.append("amount must be a digit string")
print("\n".join("FAIL: " + p for p in problems) or "PASS")
sys.exit(1 if problems else 0)
```

We ran it on 14 October 2026 against the server above (PASS, exit 0), against a catch-all path that answers 200 (FAIL, exit 1), and against aiscan.site, which gates nothing (FAIL: 2xx without payment, as expected). Pass means the response is shaped as the spec describes. It does not mean a client can pay, because the script never signs anything.

## How this differs from Cloudflare pay-per-crawl

Cloudflare's pay-per-crawl also answers with a 402, but it is a Cloudflare product with its own request headers and error codes, billed through Cloudflare. Our [pay-per-crawl guide](/blog/cloudflare-pay-per-crawl-should-you-charge) covers the decision to charge, so this guide does not repeat it. The practical difference: with pay-per-crawl you configure a price in a dashboard and Cloudflare answers for you; with x402 you run the 402 yourself. Our research notes from September 2026 found the pay-per-crawl 402 body carries an `x402Version` of `0` and a `deferred` scheme on the `cloudflare` network, so the two are not interchangeable. Re-measure before you rely on that, since Cloudflare changes the product often.

## Should you skip it?

Probably, if any of these is true:

- You publish free content and want it cited. A 402 on it blocks the agents you want.
- Your checkout is a normal cart. [UCP and the other catalogue protocols](/blog/agentic-commerce-protocols-explained) fit that better.
- You cannot hold or receive crypto-denominated payments, since the spec's `exact` scheme settles on-chain tokens.
- You have a handful of plans. Publish [machine-readable pricing](/blog/machine-readable-pricing-schema) (`check:M4`) instead, which does affect your score.

AIScan's own research notes recorded M3 passing on 0 of 180 commerce sites for three consecutive weeks in September 2026. Treat that as a snapshot of adoption, not a verdict on the protocol.

## When something goes wrong

- **The agent sees 200 and no price:** a catch-all route answered before your handler. Register the gated route first.
- **The header is there but the client cannot parse it:** you sent raw JSON instead of base64. Encode it.
- **The price looks 100 times too high or low:** `amount` is in atomic units. For six-decimal USDC, 0.01 is `10000`.
- **A CDN serves the same 402 to everyone:** add `Cache-Control: no-store` to the 402.
- **Payments fail after a retry:** your server is not calling a facilitator. Path 1 is a demo of the response, not a payment verifier.
- **The scan still says optional:** that is the normal state for a site with nothing gated.

## Next step

Scan your site with `npx aiscan-cli yoursite.com` and look at M3, M4 and M1. Most readers will leave M3 informational and fix M4. Then browse the [guide library](/guides) and the [commerce checks page](/docs/checks/commerce). The task works without us: a text editor, Python and curl are enough.

