It's January 1997.
The IETF (Internet Engineering Task Force) has just released RFC 2068, officially defining HTTP/1.1. The specification was authored by web pioneers Roy Fielding, Jim Gettys, Jeffrey Mogul, Henrik Frystyk, and Tim Berners-Lee, the architects who shaped how the internet communicates.
The specification introduces persistent connections : previously, every single HTTP request required a fresh TCP connection. Persistent connections resolve this, allowing multiple HTTP requests to flow through a single, long-lived TCP connection. No more establishing separate connections for every image, CSS file, or JavaScript snippet on a web page.
There's also chunked transfer encoding , a new way for web servers to stream content without knowing the full size beforehand. No longer does a server need to calculate the total size of dynamically generated content upfront, it's now free to deliver data incrementally, as it's produced.
But RFC 2068 quietly introduces something intriguing , a new status code:
HTTP 402 Payment Required
This code is reserved for future use.
This shows how the founding fathers of world wide web predicted how money would eventually become a big part of internet, even if they had no clear path on how it would actually play out.
Today, 2025, nearly three decades and multiple HTTP versions later (HTTP/2
in 2015, HTTP/3
in 2022), Status Code 402
still sits there with the exact same note: 'reserved for future use.' Despite the fintech revolution, the rise of online payments, and an entire economy built on internet transactions, nobody had figured out what to do with it.
Until now.
Last month (May 2025), Coinbase released x402
, an open source protocol that gives HTTP 402
its first real job: enabling native onchain payments within HTTP requests.
Nowadays AI agents need to make M2M (machine-to-machine) payments across the web with reduced HITL (human-in-the-loop) interventions, but traditional payment flows dont work well in this case. They require multiple human interactions, redirects, and manual steps that simply don't work when an AI agent needs to make a transaction autonomously.
x402
fills this gap. It proposes an automated on-chain payment flow implemented natively within the HTTP protocol, making them as seamless as any other web request.
But what does this look like in practice?
Architecture and components of x402
x402
is built around four core components:
A client acts as the payment initiator, discovering what's required for access and constructing the appropriate payment payload. Put simply, this is whatever is making the HTTP request to a pay-walled resource. It could be a browser making a request for premium content, an AI agent purchasing API access, or a mobile app unlocking features. The client handles the cryptographic signing using the user's private key and automatically retries requests when payment is required.
The resource server enforces payment policies for its endpoints while remaining focused on its core business logic. This is the web server or API that hosts the content or service being purchased. It maintains simple pricing tables that map endpoints to costs, but delegates the payment verification logic to the facilitator.
Blockchain logic is implemented in the facilitator component: verifying cryptographic signatures, preventing replay attacks through nonce tracking, and managing the actual on-chain settlement. It allows both clients and servers to work with on-chain payments without understanding the blockchain implementation details.
On blockchain resides the final settlement layer, ensuring payments are immutable and transparent. It enables programmable money through smart contracts and stable-coins, but its complexity is completely hidden from the application layer by the facilitator.
Client:
- Primary Responsibility: Payment initiation
- Key Features:EIP-712 signing, automatic retries, payment discovery
- What it does: Makes requests, handles wallets, retries with payment
Resource Server:
- Primary Responsibility: Payment enforcement
- Key Features: Pricing tables, HTTP 402 responses, middleware integration
- What it does: Sets prices, checks payments, serves content
Facilitator:
- Primary Responsibility: Payment verification
- Key Features: Signature verification, nonce tracking, gas abstraction
- What it does: Verifies signatures, talks to blockchain
Blockchain:
- Primary Responsibility: Payment settlement
- Key Features:USDC transfers, smart contracts, immutable records
- What it does: Settles payments on chain
Principles
This architecture demonstrates several fundamental software engineering principles. The most important is separation of concerns. Each component has a single, well-defined responsibility. Resource servers focus purely on business logic, facilitators handle payment complexity, and clients manage user interaction.
The system achieves loose coupling by having components interact only through standardized HTTP and REST interfaces. A resource server doesn't need to understand how blockchain transactions work, and a client doesn't need to know the server's internal implementation. This isolation means you can swap out components (for example, use a different blockchain, change facilitator providers, or modify server logic) without affecting the rest of the system.
The facilitator embodies the single responsibility principle by isolating all blockchain complexity into one specialized service. This prevents payment logic from leaking into business applications and keeps concerns properly separated.
Last but not least this architecture follows dependency inversion. High-level components depend on abstractions rather than concrete implementations. Servers and clients depend on HTTP interfaces, not specific blockchain APIs. This allows the same application code to work across different blockchains and payment schemes without modification.
Payment flow
When an AI agent or user hits an x402
-enabled API, here's the four-step flow that happens:
Initial request : The client makes a standard HTTP request to access some resource
Payment required response : If no payment is attached, the server responds with
HTTP 402
and includes payment detailsPayment authorization : The client creates a cryptographically signed payment and retries the request
Verification and access : The server validates the payment, broadcasts it to the blockchain, and grants access
What makes this powerful is that it all happens at the HTTP protocol level. No redirects to third-party payment processors, no OAuth
flows, no account creation. Just standard HTTP with extra headers:
X-PAYMENT
flows from client to server and contains the signed payment payload. This includes the payment details (amount, recipient, token) plus a cryptographic signature proving the client authorized the payment.X-PAYMENT-RESPONSE
flows from server to client after successful payment and contains transaction receipt information , providing transparency about what happened on-chain.
Server-side implementation
Payment middleware architecture
The core server-side implementation revolves around a payment filter (AKA middle-ware) that intercepts HTTP requests and enforces payment requirements. When integrated into your web server, this middle-ware checks incoming requests against a price table that maps endpoints to their costs.
The middle-ware follows a simple decision tree: if a request hits a protected endpoint without payment, it responds with HTTP 402
and detailed payment instructions. If payment is included in the X-PAYMENT
header, it verifies the payment with a facilitator service before allowing the request to proceed.
Here's the essential structure from the Java implementation:
public class PaymentFilter implements Filter {
private final String payTo;
private final Map<String, BigInteger> priceTable; // path โ amount
private final FacilitatorClient facilitator;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
String path = req.getRequestURI();
String paymentHeader = req.getHeader("X-PAYMENT");
if (!priceTable.containsKey(path)) {
chain.doFilter(request, response); // Free endpoint
return;
}
if (paymentHeader == null) {
send402Response(resp, path); // Request payment
return;
}
// Verify payment, process request, then settle
VerificationResponse verification = facilitator.verify(paymentHeader, requirements);
if (verification.valid) {
chain.doFilter(request, response);
facilitator.settle(paymentHeader, requirements);
}
}
}
The beauty of this approach is that it requires minimal changes to existing applications. You simply add the payment filter to your middle-ware stack and define which endpoints require payment.
PaymentRequirements
Response
When a client hits a protected endpoint without payment, the server constructs a detailed payment requirements object. This includes the payment amount, accepted tokens (like USDC
), the receiving wallet address, blockchain network, and an expiration time to prevent replay attacks.
private void send402Response(HttpServletResponse response, String path) throws IOException {
response.setStatus(HttpStatus.PAYMENT_REQUIRED);
response.setContentType("application/json");
PaymentRequirements requirements = PaymentRequirements.builder()
.paymentRequirement(List.of(
PaymentRequirement.builder()
.kind(new Kind("exact", "base-sepolia")) // Payment scheme + blockchain network
.receiver(payTo) // Wallet address to receive payment
.amount(priceTable.get(path)) // Cost for this specific endpoint
.asset("<USDC_TOKEN_CONTRACT>") // USDC token contract
.expiry(Instant.now().plus(Duration.ofMinutes(5))) // Payment window
.nonce(UUID.randomUUID().toString()) // One-time use identifier
.build()
))
.build();
response.getWriter().write(Json.MAPPER.writeValueAsString(requirements));
}
Each field in the PaymentRequirements
is described as follows:
kind
: Defines the payment scheme (exact
for fixed amounts) and target blockchain network (base-sepolia
for Base testnet). This tells the client exactly how to structure and execute the payment.receiver
: The wallet address where payment should be sent. This is your business wallet that will receive the funds.amount
: The cost for accessing this specific endpoint, retrieved from your price table. For USDC, this is typically expressed inwei
(smallest unit).asset
: The smart contract address of the token to be used for payment. The example shows USDC on Base Sepolia testnet.expiry
: A timestamp after which this payment requirement becomes invalid. This prevents old payment requests from being reused and adds security against replay attacks.nonce
: A unique identifier (UUID) that ensures each payment requirement can only be fulfilled once, even if the same client makes multiple requests to the same endpoint.
Client-side implementation
Automatic payment handling
Client libraries wrap standard HTTP clients to automatically handle 402 responses. When a client receives a payment requirement, (1) it constructs a payment payload, (2) signs it with the user's private key, and (3) retries the original request with the payment attached.
public HttpResponse<String> makeRequest(String url, String method) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.method(method, HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
// Handle 402 Payment Required
if (response.statusCode() == 402) {
PaymentRequirements requirements = Json.MAPPER.readValue(
response.body(), PaymentRequirements.class);
// Create payment payload matching the requirements
PaymentPayload payment = PaymentPayload.builder()
.receiver(requirements.getPaymentRequirement().get(0).getReceiver())
.amount(requirements.getPaymentRequirement().get(0).getAmount())
.asset(requirements.getPaymentRequirement().get(0).getAsset())
.nonce(requirements.getPaymentRequirement().get(0).getNonce())
.expiry(requirements.getPaymentRequirement().get(0).getExpiry())
.build();
// Sign using EIP-712 structured data signing
String signature = signer.sign(payment.toSigningMap());
// Retry with payment header
String paymentHeader = encodePaymentHeader(payment, signature);
HttpRequest paidRequest = HttpRequest.newBuilder()
.uri(URI.create(url))
.method(method, HttpRequest.BodyPublishers.noBody())
.header("X-PAYMENT", paymentHeader)
.build();
return httpClient.send(paidRequest, HttpResponse.BodyHandlers.ofString());
}
return response;
}
The signing process uses the EIP-712
standard, which creates a structured, human-readable representation of the payment data before hashing and signing it. This ensures the payment is cryptographically secure and tied to the specific request.
Payment verification flow
Facilitator integration
The facilitator service is where the blockchain complexity lives, abstracting it away from both clients and servers. When a server receives a payment, it forwards the payment payload to the facilitator for verification.
public class HttpFacilitatorClient implements FacilitatorClient {
private final HttpClient http;
private final String baseUrl;
@Override
public VerificationResponse verify(String paymentHeader, PaymentRequirements requirements)
throws Exception {
// Construct verification request with payment and requirements
VerifyRequest body = VerifyRequest.builder()
.paymentHeader(paymentHeader) // The X-PAYMENT header from client
.requirements(requirements) // What the server expects
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/verify"))
.POST(HttpRequest.BodyPublishers.ofString(Json.MAPPER.writeValueAsString(body)))
.header("Content-Type", "application/json")
.build();
String json = http.send(request, HttpResponse.BodyHandlers.ofString()).body();
return Json.MAPPER.readValue(json, VerificationResponse.class);
}
@Override
public SettlementResponse settle(String paymentHeader, PaymentRequirements requirements)
throws Exception {
// Settlement happens after successful verification
SettleRequest body = SettleRequest.builder()
.paymentHeader(paymentHeader)
.requirements(requirements)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/settle"))
.POST(HttpRequest.BodyPublishers.ofString(Json.MAPPER.writeValueAsString(body)))
.header("Content-Type", "application/json")
.build();
String json = http.send(request, HttpResponse.BodyHandlers.ofString()).body();
return Json.MAPPER.readValue(json, SettlementResponse.class);
}
}
The facilitator checks several things:
Is the signature valid?
Does the payment amount match the requirements?
Has this payment been used before?
Is the payment still within its expiration window?
If verification passes, the facilitator also handles settlement by broadcasting the transaction to the blockchain of choice. The FacilitatorClient
interface defines the contract that any facilitator client must implement:
public interface FacilitatorClient {
VerificationResponse verify(String paymentHeader, PaymentRequirements requirements);
SettlementResponse settle(String paymentHeader, PaymentRequirements requirements);
}
Your application needs to provide a concrete implementation of this interface.
Integration example
Now that we've seen the individual components (payment filters, facilitator integration, and client handling) let's look at how these pieces come together in a real application. Here's a minimal Spring Boot
example that demonstrates the complete flow.
First, create a @PaymentRequired
annotation:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PaymentRequired {
String price();
String currency() default "USDC";
String network() default "base-sepolia";
}
Then modify the PaymentFilter
to scan for these annotations at startup:
@Component
public class PaymentFilter implements Filter {
private final Map<String, BigInteger> priceTable;
private final String payTo;
private final FacilitatorClient facilitator;
public PaymentFilter(ApplicationContext context, String payTo, FacilitatorClient facilitator) {
this.payTo = payTo;
this.facilitator = facilitator;
this.priceTable = buildPriceTableFromAnnotations(context);
}
private Map<String, BigInteger> buildPriceTableFromAnnotations(ApplicationContext context) {
Map<String, BigInteger> prices = new HashMap<>();
// Scan all @RestController beans for @PaymentRequired annotations
Map<String, Object> controllers = context.getBeansWithAnnotation(RestController.class);
for (Object controller : controllers.values()) {
Method[] methods = controller.getClass().getMethods();
for (Method method : methods) {
PaymentRequired payment = method.getAnnotation(PaymentRequired.class);
if (payment != null) {
String path = extractPathFromMapping(method);
BigInteger amount = new BigInteger(payment.price().replace(".", ""));
prices.put(path, amount);
}
}
}
return prices;
}
}
Now you can annotate your controller methods directly:
@RestController
public class WeatherController {
@GetMapping("/weather")
@PaymentRequired(price = "0.001", currency = "USDC", network = "base-sepolia")
public WeatherData getWeather(@RequestParam String city) {
// Your existing business logic
return weatherService.getWeatherForCity(city);
}
@GetMapping("/premium-forecast")
@PaymentRequired(price = "0.01", currency = "USDC", network = "base-sepolia")
public ExtendedForecast getPremiumForecast(@RequestParam String city) {
return weatherService.getExtendedForecast(city);
}
}
@Configuration
public class PaymentConfig {
@Bean
public PaymentFilter paymentFilter(ApplicationContext context) {
return new PaymentFilter(
context,
"<WALLET_ADDRESS>", // Your wallet address
new HttpFacilitatorClient("<FACILITATOR_URL>")
);
}
@Bean
public FilterRegistrationBean<PaymentFilter> paymentFilterRegistration(PaymentFilter filter) {
FilterRegistrationBean<PaymentFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(filter);
registration.addUrlPatterns("/*");
registration.setOrder(1);
return registration;
}
}
The @PaymentRequired
annotation handles pricing configuration declaratively, while the PaymentFilter
automatically discovers these annotations at startup and builds the price table. Your existing business logic in the controller methods remains completely unchanged. The configuration wires everything together by registering the payment filter and connecting it to a facilitator service. Once deployed, requests to /weather
cost 0.001 USDC and /premium-forecast
costs 0.01 USDC, with all payment handling happening transparently at the HTTP layer.
Security and production considerations
x402
is simple and elegant, it hides complexity. This is a pro and a con. It makes integration easy, but it also hides an important aspect: putting AI agents in charge of money creates new attack vectors.
While EIP-712
signatures and nonce management handle replay attacks, what happens when an agent gets compromised? Traditional fraud detection relies on human behavioral patterns, but AI agents don't follow human spending habits. A compromised agent could drain funds faster than any human fraudster.
The facilitator component becomes another high-value target since it's verifying signatures and managing nonces. Unlike traditional payment processors that can reverse transactions, blockchain settlements are final.
Since x402
is based on on-chain transactions, it inherits the operational risks of blockchain transactions. Gas fees fluctuate wildly, sometimes making micropayments economically inconvenient. Network congestion can delay transactions. What's an AI agent supposed to do when it needs real-time data but the payment is stuck in a mempool?
Another important aspect is regulation. Compliance varies across jurisdictions with different rules about automated payments, cryptocurrency usage, and data retention. An AI agent making a large volume of micro-transactions across borders might trigger AML alerts or violate local regulations without anyone realizing it.
What's next
What's interesting about x402
is the timing. AI agents need autonomous payment capabilities, stablecoins provide a programmable money layer, and blockchain infrastructure has matured enough to handle scalable applications. These pieces haven't aligned before. Internet now needs a new form of payments, and traditional flows weren't built for autonomous agents or micropayments.
x402
is compelling thanks to its pragmatic approach. Instead of reinventing payments from scratch, it extends existing HTTP infrastructure. Instead of requiring new integrations, it works with standard patterns that we already understand.
The security and operational challenges are real, but they're engineering problems with possible solutions.
After nearly three decades, HTTP 402
might finally do what it was designed for: make paying for things on the internet as simple as requesting them.
The foundation is set. Now it's time to build.
Thanks Erik Reppel, Ronnie Caspers, Kevin Leffew, Danny Organ, and all the team at Coinbase for open sourcing this protocol.
Thanks Erik Reppel and Yuga Cohler for reviewing my contributions to x402
.
Resources and Further Reading
HTTP 402 Payment Required - RFC 2068 - Original 1997 HTTP specification
x402 Protocol Specification - Official protocol documentation
x402 GitHub Repository - Coinbase's open source implementation
x402 Java implementation - The PR that introduced the Java implementation of the protocol
EIP-712: Ethereum Typed Structured Data Hashing and Signing - Signing standard used in x402
Base Network Documentation - Layer 2 blockchain platform used in examples
USDC Documentation - USD Coin stablecoin contract details
Spring Boot Filter Documentation - Java middleware implementation
Java HTTP Client API - Client-side HTTP handling
Machine-to-Machine (M2M) Communication Standards - Autonomous system communication
Autonomous AI Agent Architectures - Research on AI agent design patterns
Googles Approach to Secure AI Agents - Google propositions of a secure, human-guided AI agent framework
Top comments (2)
Mind. Blown. This is amazing. Great detail. Thank you, thank you, THANK YOU!!!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.