Photo by Mika Baumeister on Unsplash In the early 1990s, the HTTP specification reserved a special status code for payments, the “402 Payment Required.” sPhoto by Mika Baumeister on Unsplash In the early 1990s, the HTTP specification reserved a special status code for payments, the “402 Payment Required.” s

HTTP 402 Died in the 90s. Solana Just Brought It Back.

2026/03/31 00:17
18 min read
For feedback or concerns regarding this content, please contact us at crypto.news@mexc.com
Photo by Mika Baumeister on Unsplash

In the early 1990s, the HTTP specification reserved a special status code for payments, the “402 Payment Required.” status. The idea? Websites could charge for content directly through the browser. Fast forward 25+ years, and 402 is still sitting there, lonely and unused, just like that gym membership you S-W-O-R-E you’d use.

The reason behind 402 is simple: traditional payment rails are too slow and expensive for small payments. Credit card processing takes days to settle, usually costs 2–3% in fees, and requires users to trust merchants with sensitive payment info. For microtransactions like a $3 code review or even a 1$ virtual “buy me a coffee”, it’s totally impractical.

Solana and HTTP 402 Sitting in a Tree. K-I-S-S….

So we understand that traditional payment rails make micropayments economically impossible due to costs that can end up taking a hefty chunk of your hard-earned dollars. But what if we could make HTTP 402 actually work?

Now I can already hear the crypto bros in the back screaming “CRYPTO! CRYPTO IS THE ANSWER TO ALL YOUR PROBLEMS IN LIFE!” and while that is true for mostly everything, it’s not going to work in our microtransactions case. A bitcoin payment can take a long time (10–60 minutes usually) to get mined, not to mention bitcoin fees are not that small. But Solana…

Solana changes the game completely:

  • Transaction Cost: ~$0.00025 per transaction (yes, that’s about $0.25 for 1,000 transactions)
  • Speed: 400ms average confirmation time with sub-second finality (and with the upcoming Alpenglow upgrade this is going to be even faster).
  • Throughput: 3,000–5,000 TPS in practice (65,000 TPS theoretical max)
  • No Intermediaries: True peer-to-peer payments, no hungry middleman taking a cut.

This means that if you charge $0.50 for an article, you will get to keep $0.4999 of it. The user gets instant access — no loading screens, no “processing payment” spinners. Just pay -> verify -> content unlocked!

To help facilitate the use of HTTP 402 and crypto, the wonderful champs over at Coinbase created the x402 protocol, which in Coinbase’s own words is

The x402 protocol sets a standard for implementing the HTTP 402 status code using cryptocurrency payments. The concept is quite elegant: when a server wants payment for content, it responds with 402 status code and includes payment details (recipient address, amount, payment method) in the response headers and payload. The client processes the payment on-chain, then retries the request with cryptographic proof of payment. The server verifies the payment and returns the content. No payment processors, no middlemen, not even a goddamn pigeon moving your money from place to place. Just pure peer-to-peer value transfer.

Now (there’s always a now), while it’s true that the x402 library does provide various implementations and facilitators for different blockchains, its Solana support is still in its infancy, and therefore in this post we are going to implement the HTTP 402 pattern ourselves based on the x402 protocol 🙌.

Implementing the protocol ourselves will allow us to see exactly how 402 works under the hood, and as a bonus, we won’t need to deal with package dependencies, or limit ourselves to a certain supported token.

You should think of x402 as a blueprint, and we’re building a house following its design.

The HTTP 402 Pattern (Inspired by x402)

The x402 protocol is built around a simple request-response-retry pattern. Here’s what happens under the hood:

1. Initial Request: A client (user, AI agent, etc.) makes an HTTP request to access a resource.

2. 402 Response: The server responds with HTTP 402 Payment Required and includes payment details in both headers and response body:

HTTP/1.1 402 Payment Required
WWW-Authenticate: Solana realm="api"
x-Payment-Address: 7xK8...q2v
x-Payment-Amount: 0.001
x-Payment-Currency: SOL
x-Content-Type: application/json
{
"error": "payment_required",
"message": "Payment required to access this endpoint",
"payment": {
"address": "7xK8...q2v",
"amount": 0.001,
"currency": "SOL",
"network": "solana-devnet"
}
}

3. Client Processes Payment: The client creates and signs a blockchain transaction transferring the requested amount to the recipient address

4. Transaction Broadcast: The transaction is broadcast to Solana and waits for confirmation

5. Retry with Proof: The client retries the original request, this time including the transaction signature as proof of payment:

POST /api/chat
Content-Type: application/json
Payment-Signature: E37k...x2P
{
"message": "Hello!"
}

6. Server Verification: The server fetches the transaction from the blockchain and verifies that:

  • The transaction actually exists and was successful
  • The correct amount was sent
  • The payment recipient matches the server’s address
  • The transaction is confirmed (not pending or failed)
  • **The signature hasn’t been used before** (replay attack prevention)

7. Content Delivery: If all verification passes, server responds with HTTP 200 OK and the requested content. If not, back to 402!

For us visual bros here’s a nice diagram demonstrating the whole flow:

📣 It’s important to note that while we’re following the 402 pattern that x402 pioneered, our implementation is streamlined:

  • We use native SOL.
  • We skip the facilitator and verify transactions on-chain directly.
  • We use a simple Payment-Signature header instead of complex payloads
  • We track used signatures server-side to prevent replay attacks

The beauty of the 402 pattern is that it’s just HTTP — you can implement it however you want, as long as you follow the basic flow.

Let’s Build Some Sh*t!

Someone who actually build sh*t

Enough theory — time to get our hands dirty. We’re going to build a complete HTTP 402 payment system using Solana, for fun and games of course.

So, What Are We Building?

We’re creating a 3-part system that demonstrates HTTP 402 in action:

  • API Server — A backend that returns 402 responses and verifies Solana payments
  • AI Agent — A CLI tool that autonomously detects and handles 402 payments
  • A surprise(?)

The flow is simple but powerful: The agent makes a request → API returns 402 → Agent pays autonomously → API verifies payment → Content delivered. No accounts, no credit cards, no payment processors. Just pure peer-to-peer value transfer awesomeness.

Tech Stack

Here’s what we’re working with:

  • Backend: Hono — A fast, lightweight web framework (think Express but better)
  • Blockchain: @solana/kit — The latest and greatest Solana SDK
  • AI: Anthropic Claude API — For the actual AI responses (you can swap this with any API)
  • Language: TypeScript throughout — Because we’re not savages 😂

Project Structure

http-402-demo/
├── api/ # Backend API Server
│ ├── src/
│ │ └── index.ts # Main API with 402 logic
│ ├── package.json
│ └── .env.example

├── agent/ # Autonomous AI Agent
│ ├── src/
│ │ └── index.ts # Agent that handles 402s
│ ├── package.json
│ └── .env.example

└── README.md

Prerequisites

Before we dive in, make sure you have:

  • Node.js 22+ — Download here
  • Some devnet SOL — Get free devnet SOL from solfaucet.com
  • Anthropic API key (optional) — Get one here (free tier works fine)

LFG!

let’s start by cloning the repo:

git clone https://github.com/jtordgeman/http-402-starter.git
cd http-402-starter

The starter branch has everything scaffolded — we just need to fill in the 402 payment logic as we go. If you get stuck, you can always peek at the finished branch for the complete implementation.

We kick things off with the API server — the ♥️ of our HTTP 402 system!

Building the API Server

Our API has literally one job: guard content behind a paywall that only accepts Solana transactions as payment. Simple and to the point.

First, install dependencies and set up your environment:

cd api
npm install
cp .env.example .env

Open the .env file and fill in your values:

PAYMENT_ADDRESS=<your_solana_wallet_address> # Where payments will go
PAYMENT_AMOUNT=0.001 # Amount in SOL
SOLANA_RPC_URL=https://api.devnet.solana.com # Use devnet for testing
ANTHROPIC_API_KEY=<your_key_here> # Optional, enables real AI responses

Our server is built with Hono — think Express but faster and TypeScript-first. The imports and configuration are already set up for you in the starter project:

https://medium.com/media/d4d8bf28cd625dfed7d0ffdaa47da1f9/href

See that createSolanaRpc call? That’s the Solana kit way of connecting to the blockchain. If you’re coming from Web3.js 1.x, this used to look like:

import { Connection } from '@solana/web3.js';
const connection = new Connection('https://api.devnet.solana.com', 'confirmed');

Step 1: Replay Attack Prevention

Our first order of business is to take care of security. Imagine this loophole: someone paid one of our 402 requests and now they are reusing the same transaction signature over and over again. Each Solana signature is unique per transaction, so if we store every signature we’ve seen, we can instantly reject any that’s been used before. Easiest way to do this in TypeScript? A set. Why is it perfect for us?

  • Set lookup O(1), making it lightning fast even with millions of entries.
  • Set automatically handles duplicates
  • It’s dead easy to use: .has() to check, .add() to mark used.

Add the processedSignatures set right after the service initialization:

const processedSignatures = new Set<string>();

Step 2: Verifying the Payment

This is the most important function in the whole project. When a client claims they paid, we go directly to the Solana blockchain to verify their claim. It’s not that we don’t trust them, it’s just that we don’t trust them :) (also, the blockchain doesn’t lie!)

https://medium.com/media/52febba109d141c39ccc878b3716bf5a/href

So what’s going on here?

  • Line 2: We get the instructions of the transaction from the tx object.
  • Line 3: We need to convert the expectedAmount from SOL to lamports: 1 SOL = 1,000,000,000 lamports. It’s like satoshis for Bitcoin. When we verify the transaction, we compare lamports to lamports, not SOL to SOL.
  • Lines 4–15: We iterate over the transaction’s instructions checking if the instruction is parsed, if so we verify it’s a transfer type and only then we verify that the transaction is for the correct address and amount. We used ≥ instead of === for the lamports verification so in case the user sent more than we require, we just treat it as a “tip” and keep it 😈
  • If we find a transaction that fits all the details — we return true, otherwise false

That’s it for our legendary verify payments method. Let’s move on to the chat route.

legendary indeed

Step 3: The chat Endpoint

This is where all the pieces come together, a.k.a the full HTTP 402 awesomeness. The chat route reads like a bouncer at a club: no ticket/bad ticket? no entry. Only once everything checks out do you get in.

Update the /api/chat handler as follows:

https://medium.com/media/1dcbe20bdee7ce9dba994eba76eb108f/href

Let’s walk through it:

  • Lines 2–3: We grab the Payment-Signature header and request body. The signature is how the client proves they paid.
  • Lines 7–9: No payment signature? call payment402Response to handle all the response details.
  • Lines 12–14: Before touching the blockchain, we check our processedSignatures Set. Already seen this signature? Replay attack. 400 and goodbye.
  • Lines 17–22: We are getting the transaction details from the blockchain by passing the signature string directly as a Signature type to the getTransaction call. We call the getTransaction’s .send() method to actually execute the RPC call.
  • Lines 24–32: Three sequential checks on the transaction: did the transaction exist? Did it succeed? Did it actually pay the right amount to the right address? if any of these checks fail, we respond back with a 402 response to the client.
  • Line 35: Only NOW do we mark the signature as used. After the verification passes but before actually serving content. If we did it earlier and content delivery failed, the user would lose their payment for nothing.
  • Lines 37–41: The payment is verified 🎉 we can now call the generateAiResponse function to generate the response body and send back a lovely status 200 response.

With the main method all laid out, let’s add the support methods:

https://medium.com/media/9da2a63d3745412e0a862c0e27fe5769/href

So what do we have here?

  • payment402Response is pretty straightforward. All it does is return a 402 payment request stating the address, amount, currency and network in the body of the response. In addition to that the response also include a bunch of headers that describe the payment as well
  • generateAiResponse generates the AI response, real or mocked. The block.type === "text" check is there because the Anthropic SDK returns a union type for content blocks — it could be text, a tool call, or other types — so we narrow it properly instead of blindly accessing .text.

Step 4: Test Run

Let’s do a quick test just to verify everything is working smoothly. Start the API server:

npm run dev

Run a quick test to verify the 402 gated endpoint:

curl -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-d '{"message": "hello"}'

If all went well you should get back a response similar to this:

{
"error": "payment_required",
"message": "Payment required to access this endpoint",
"payment": {
"address": "YourAddress...",
"amount": 0.001,
"currency": "SOL",
"network": "solana-devnet"
}

Beautiful. Our API is now refusing to work for free 💸. Next, it’s time to build the agent that pays the bills.

Building the Agent 🤖

If we imagined our API as the paywall guard, the agent is that club-goer friend we all have, who knows how to talk their way in (or in our case, pay their way in). The agent’s only job is to make a request, handle the 402 like a champ, and retry with payment proof. Let’s build it.

First, just like before, install dependencies and set up your environment:

cd agent
npm install
cp .env.example .env

Open the .env file and fill in your values:

API_URL=http://localhost:3000 # Your API server
AGENT_WALLET_KEY=your_private_key # Agent's Solana wallet (for autonomous payments)
SOLANA_RPC_URL=https://api.devnet.solana.com

Step 1: Making the Request

This is the heart of the agent, the method that orchestrates the entire 402 flow. It starts by making a normal request, just like any HTTP client would. When we get a 402 back, we parse the body into this shape:

https://medium.com/media/5e8a6e89dd76bf02b6a8f171ec9035b1/href

The API already sends all of these properties (easily verifiable, just check the API project response), we’re just giving it a type so TypeScript keeps us honest. Now let’s actually use it. Add the following to your src/index.ts file:

https://medium.com/media/e27c93150f8c54a30d5e68b86124bd21/href

Before you ask, yes we use two methods to handle the request: fetchChat is a dead-simple private helper. Its one job is to fire the request and optionally include the Payment-Signature header. makeRequest is the method that owns all the flow:

  • Line 13: we make a call to fetchChat with our message. simple and to the point.
  • Lines 15–27: we check if the status is 402. If it is, we destruct payment straight from the body and hand it to handlePayment, which signs and broadcasts the transaction autonomously. When we get back the response from handlePayment we retry the API call with the payment signature. If the API rejects it again, we throw with the error message.

The 402 flow doesn’t look so scary now does it?

Step 2: Handling the Payment

Now comes the real fun part, we are finally touching those sweet sweet 💸💸! When makeRequest hits a 402, it hands off to handlePayment which takes care of the payment entirely on its own. No user input, no manual signing, no readline prompts.

pure magic indeed

First, have a look at the constructor of the AIAgent class:

https://medium.com/media/6714d081e40d1c73e1f33fc4eccaadbb/href

The constructor wires all the RPC related functionality we need:

  • It creates a new Solana RPC (this.rpc)
  • It creates a WebSocket subscription so we can know when our transaction is confirmed
  • It creates a sendAndConfirm function using sendAndConfirmTransactionFactory, which combines the RPC and the WebSocket subscription into a single call that submits a transaction and waits until the network confirms it landed — so when it returns, you know the payment actually went through rather than just being submitted.

Next, add the handlePayment method to the src/index.ts file as follows:

https://medium.com/media/f397978d421a413458e86ad804f3f594/href

A few things worth calling out:

  • getLatestBlockhash: every Solana transaction needs a blockhash to set its expiry window. This is how the network knows a transaction isn’t stale.
  • transferInstruction: we use the system program’s transfer instruction to move a specified amount of SOL (amount) from one wallet (the source) to another (the destination).
  • pipe and transactionMessage: a Solana transaction is three things: who pays the fee, when it expires, and what it does. pipe is how @solana/kit makes you assemble those pieces — start with a blank message, add the fee payer (setTransactionMessageFeePayerSigner), set the expiry window aka blockhash (setTransactionMessageLifetimeUsingBlockhash) and finally tell it what to actually do by appending one or more instructions (appendTransactionMessageInstruction). Each step returns a new message rather than mutating the old one. Super explicit with no hidden state.
  • signTransactionMessageWithSigners — signs the transaction message with the wallet’s private key. Returns a fully signed transaction ready to broadcast.
  • getSignatureFromTransaction — extracts the signature from the signed tx. In Solana, the signature is the transaction ID — you can plug it into Solana Explorer to inspect the tx even before it’s confirmed.
  • sendAndConfirm — broadcasts the transaction and waits for confirmation. commitment: "confirmed" means roughly 2/3 of validators have seen it — fast enough for our use case and more reliable than processed.

Step 3: Test Run

Start your API server first (in a separate terminal):

cd api && npm run dev

Then run the agent:

cd agent && npm start

And if everything went well you should see this awesomeness printed out:

════════════════════════════════════════════════════════════
🤖 HTTP 402 Autonomous Agent Demo
════════════════════════════════════════════════════════════
📍 API: http://localhost:3000
🌐 Network: https://api.devnet.solana.com
════════════════════════════════════════════════════════════
✅ Agent wallet loaded
402 response received {
error: 'payment_required',
message: 'Payment required to access this endpoint',
payment: {
address: '5b9r8...KDSM',
amount: 0.001,
currency: 'SOL',
network: 'solana-devnet'
}
}
💰 Paying 0.001 SOL autonomously...
✅ Payment confirmed: 3i5Ak...tX8Qx
Agent response {
success: true,
response: 'Mock AI response to: "Explain HTTP 402 in one sentence"',
payment: {
signature: '3i5Akiq...X8Qx',
verified: true
}
}
✅ Demo completed successfully!

🖐🎤 we finished the whole 402 process, no prompts, no manual steps. The agent detects the 402 response, pays up and retry the request with the proof of payment (signature).

A 402 MCP?

So we built a CLI agent that handles 402s autonomously. But I have a feeling a thought just crossed your mind: what if Claude Code itself could handle HTTP 402 payments natively? Well, that’s exactly what MCP (Model Context Protocol) makes possible.

We already know how to handle payment from our CLI agent, so what will happen if we use the same logic in a MCP tool? 🤔

The 402 MCP Server

The entirety of the MCP server can be found in the /mcp-server/src/index.ts file:

https://medium.com/media/efbd6b31d6839e3dfa156a314848af42/href

Few things to point out:

  • The handlePayment function is identical to what we wrote for the agent. The only real difference is the wrapper. Instead of a class method calling it on a 402, it’s the MCP tool doing the same thing.
  • registerTool takes the schema separately from the handler. The first argument is the tool name, the second is metadata + inputSchema (a Zod object), and the third is the async handler. This separation is intentional — it lets MCP clients inspect the tool's shape without executing it.
  • solToLamports uses string arithmetic, not floats. 0.001 * 1_000_000_000 in floating point isn’t always exact. We split the decimal string, pad to 9 digits, and do pure BigInt math.
  • We use Solana’s native system program to create the transfer instruction (getTransferSolInstruction)

Running the MCP server

First, install dependencies and set up your environment:

cd mcp-server
npm install
cp .env.example .env

Make sure you update the .env file with your values:

AGENT_WALLET_KEY=<Your agent wallet private key>
SOLANA_RPC_URL=https://api.devnet.solana.com

For Claude to be able to use the server we must add a .mcp.json file describing the MCP first. When we run claude in our project main folder it will pick up that file and register the MCP for use.

Create a .mcp.json file in the root of the project (same level as agent and api folders) and make sure to update the agent wallet key as well:

{
"mcpServers": {
"http-402-payments": {
"command": "node",
"args": ["./mcp-server/dist/index.js"],
"env": {
"AGENT_WALLET_KEY": "YOUR_BASE58_PRIVATE_KEY",
"SOLANA_RPC_URL": "https://api.devnet.solana.com"
}
}
}
}

Build the server (npm run build inside mcp-server/), then launch Claude Code from your project root.

Seeing it in Action

With the API server running and the MCP server loaded, you can just ask Claude Code naturally:

Claude Code will call make_paid_request, get the 402, pay autonomously, and retry — without you writing any orchestration code. Here's what that looks like:

Look Ma! I can pay on my own!

There we go, Claude picked up our MCP and actually paid for its own transaction using 402 🎉🎉🎉

If we look at the bigger picture, HTTP 402 and MCP is a natural fit. MCP tools are how AI agents interact with the world. Payment-gated APIs are how that world charges for access. The two were always going to meet, we’re just doing it a bit early.

Wrapping Up

HTTP 402 has been sitting in the spec since the early 1990s. The reason it never took off is that the payment layer was always the hard part — credit cards have friction, subscriptions need accounts, micropayments had no good infrastructure. Solana changes that. And AI agents make it worth building, they don’t need onboarding flows, they don’t forget to cancel subscriptions, they just need to pay for what they use, automatically, in the moment they need it.

In this post we demonstrated a payment-gated API, an agent that handles the flow end-to-end, and an MCP server that gives Claude Code the same ability with zero orchestration code.

There’s still more to explore of course. The code in this post is nowhere near production grade, and we didn’t even touch on the web frontend side as wiring 402 into a React app with wallets like Phantom or Solflare is a whole different flow. If you’d like to see a follow-up post on that, let me know in the comments.


HTTP 402 Died in the 90s. Solana Just Brought It Back. was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

Disclaimer: The articles reposted on this site are sourced from public platforms and are provided for informational purposes only. They do not necessarily reflect the views of MEXC. All rights remain with the original authors. If you believe any content infringes on third-party rights, please contact crypto.news@mexc.com for removal. MEXC makes no guarantees regarding the accuracy, completeness, or timeliness of the content and is not responsible for any actions taken based on the information provided. The content does not constitute financial, legal, or other professional advice, nor should it be considered a recommendation or endorsement by MEXC.