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. IETF IETF RFC 2068 RFC 2068 HTTP/1.1 Roy Fielding Roy Fielding Jim Gettys Jim Gettys Jeffrey Mogul Jeffrey Mogul Henrik Frystyk Henrik Frystyk Tim Berners-Lee Tim Berners-Lee 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. persistent connections 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. chunked transfer encoding But RFC 2068 quietly introduces something intriguing , a new status code: RFC 2068 quietly introduces something intriguing HTTP 402 Payment Required This code is reserved for future use. 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. 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. HTTP/2 HTTP/3 Status Code 402 still sits there with the exact same note: 'reserved for future use.' Until now. 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. Coinbase x402 HTTP 402 onchain 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. M2M HITL 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. x402 making them as seamless as any other web request But what does this look like in practice? Architecture and components of x402 x402 x402 is built around four core components: x402 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. client 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. resource server 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. facilitator 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. blockchain but its complexity is completely hidden from the application layer by the facilitator Client: Client Primary Responsibility: Payment initiation Key Features:EIP-712 signing, automatic retries, payment discovery What it does: Makes requests, handles wallets, retries with payment 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: Resource Server Primary Responsibility: Payment enforcement Key Features: Pricing tables, HTTP 402 responses, middleware integration What it does: Sets prices, checks payments, serves content Primary Responsibility: Payment enforcement Key Features: Pricing tables, HTTP 402 responses, middleware integration What it does: Sets prices, checks payments, serves content Facilitator: Facilitator Primary Responsibility: Payment verification Key Features: Signature verification, nonce tracking, gas abstraction What it does: Verifies signatures, talks to blockchain Primary Responsibility: Payment verification Key Features: Signature verification, nonce tracking, gas abstraction What it does: Verifies signatures, talks to blockchain Blockchain: Blockchain Primary Responsibility: Payment settlement Key Features:USDC transfers, smart contracts, immutable records What it does: Settles payments on chain 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. separation of concerns 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. loose coupling 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 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. single responsibility principle 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. dependency inversion Payment flow When an AI agent or user hits an x402-enabled API, here's the four-step flow that happens: x402 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 details Payment 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 Initial request : The client makes a standard HTTP request to access some resource Initial request Payment required response : If no payment is attached, the server responds with HTTP 402 and includes payment details Payment required response HTTP 402 Payment authorization : The client creates a cryptographically signed payment and retries the request Payment authorization Verification and access : The server validates the payment, broadcasts it to the blockchain, and grants access Verification and 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: OAuth 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. 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 signed payment payload X-PAYMENT-RESPONSE flows from server to client after successful payment and contains transaction receipt information , providing transparency about what happened on-chain. X-PAYMENT-RESPONSE transaction receipt information 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. HTTP 402 X-PAYMENT 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); } } } 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. The beauty of this approach is that it requires minimal changes to existing applications. PaymentRequirements Response PaymentRequirements 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. USDC 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)); } 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: PaymentRequirements 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 in wei (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. 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. kind exact base-sepolia receiver: The wallet address where payment should be sent. This is your business wallet that will receive the funds. receiver amount: The cost for accessing this specific endpoint, retrieved from your price table. For USDC, this is typically expressed in wei (smallest unit). amount wei asset: The smart contract address of the token to be used for payment. The example shows USDC on Base Sepolia testnet. asset expiry: A timestamp after which this payment requirement becomes invalid. This prevents old payment requests from being reused and adds security against replay attacks. expiry 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. nonce 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; } 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. EIP-712 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); } } 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? 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: FacilitatorClient public interface FacilitatorClient { VerificationResponse verify(String paymentHeader, PaymentRequirements requirements); SettlementResponse settle(String paymentHeader, PaymentRequirements requirements); } 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. Spring Boot First, create a @PaymentRequired annotation: @PaymentRequired @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PaymentRequired { String price(); String currency() default "USDC"; String network() default "base-sepolia"; } @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: PaymentFilter @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; } } @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; } } @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. @PaymentRequired PaymentFilter /weather /premium-forecast 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. x402 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. EIP-712 AI agents don't follow human spending habits 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. 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? x402 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 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. x402 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. HTTP 402 The foundation is set. Now it's time to build. 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, Erik Reppel Ronnie Caspers, Ronnie Caspers Kevin Leffew, Kevin Leffew Danny Organ, and all the team at Danny Organ Coinbase Coinbase for open sourcing this protocol. Thanks Erik Reppel and Yuga Cohler for reviewing my contributions to x402. Thanks Erik Reppel Erik Reppel and Yuga Cohler Yuga Cohler for reviewing my contributions 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 HTTP 402 Payment Required - RFC 2068 - Original 1997 HTTP specification HTTP 402 Payment Required - RFC 2068 x402 Protocol Specification - Official protocol documentation x402 Protocol Specification x402 GitHub Repository - Coinbase's open source implementation x402 GitHub Repository x402 Java implementation - The PR that introduced the Java implementation of the protocol x402 Java implementation EIP-712: Ethereum Typed Structured Data Hashing and Signing - Signing standard used in x402 EIP-712: Ethereum Typed Structured Data Hashing and Signing Base Network Documentation - Layer 2 blockchain platform used in examples Base Network Documentation USDC Documentation - USD Coin stablecoin contract details USDC Documentation Spring Boot Filter Documentation - Java middleware implementation Spring Boot Filter Documentation Java HTTP Client API - Client-side HTTP handling Java HTTP Client API Machine-to-Machine (M2M) Communication Standards - Autonomous system communication Machine-to-Machine (M2M) Communication Standards Autonomous AI Agent Architectures - Research on AI agent design patterns Autonomous AI Agent Architectures Googles Approach to Secure AI Agents - Google propositions of a secure, human-guided AI agent framework Googles Approach to Secure AI Agents