Building a Pay-Per-Request Blockchain API with x402 + GetBlock
Build a pay-per-request blockchain data API using the x402 protocol and GetBlock's node infrastructure.
Overview
The x402 protocol is an open payment method that enables developers and service providers to charge/sell their APIs and content via HTTP without requiring third-party integrations, credential setup, or gas fees.
The x402 protocol brings the HTTP 402 "Payment Required" status code to life. Originally reserved in the HTTP specification for future use with digital payments, x402 finally implements this vision, enabling micropayments to be made directly over HTTP.
How it works
Frank adds "payment required" to his API endpoints
His client requests a protected resource,
The server responds with 402 Payment Required along with payment details.
The client signs a payment authorisation
He/she retries the request and receives the data.
All of this happens in seconds, without traditional payment infrastructure.
Global access β Anyone with USDC can use your API
Subscription tiers and billing cycles
Instant micropayments β Charge per request
Fraud prevention and rate limiting
Built-in rate limiting β Users only call what they pay for
Transaction or gas fee
Gasless fee
Components of x402
The x402 ecosystem consists of three main components, which are:
Client Side:
This is the interface e.g frontend, that users interact with, which initiates a request to access a paid resource. It handles the payment requirements, prepare payment payload and resubmits the request with payment.
Clients do not need to manage accounts, credentials, or session tokens beyond their crypto wallet. All interactions are stateless and occur over standard HTTP requests.
Server Side:
The server is the resource provider enforcing payment for access to its services, such as API services, content providers, or any HTTP-accessible resource requiring monetization. It defines payment requirements, verifies payment payloads, settles transactions, and provides the resources.
Servers do not need to manage client identities or maintain session state. Verification and settlement are handled per request.
Facilitators:
The facilitator is an optional but recommended service that verifies and settles payment between clients and servers.
Architecture Overview
Key Components Explained:
Component
Role
SDK to Use
Client (dApp)
Browser app with wallet connection. Uses @x402/fetch to handle payments automatically.
Replace 0xYourWalletAddressHere with your actual MetaMask wallet address. This is where you'll receive USDC payments.
Step 2: Create the Server
Create server/server.js and add the following code:
Frontend: Vanilla JS dApp
Create client/index.html and add the following code:
Running the Application
Step 1: Start the Server
You should see:
Step 2: Access the Frontend
Open your browser and navigate to:
Your frontend should look like this:
Connect your wallet
Select any of the endpoints you want to access
Sign the request
The payment is then verified, scroll down to see the result
Result of the latest block number
Troubleshooting
"Failed to create payment payload: Address undefined": This means the wallet client was not properly initialized. Make sure you're using getAddress() to normalize the address:
CORS Errors: This means the server not configured for x402 headers. Ensure CORS middleware includes x402 headers:
"Facilitator does not support scheme": This mean the facilitator is unreachable or doesn't support your network. To resolve this:
i. Check internet connection
ii. Try an alternative facilitator: https://facilitator.payai.network
iii. Verify you're using the correct network ID: eip155:84532
"Facilitator Connectivity Issues"
If the default facilitator is down, you can try:
Wallet Signing Problems
If MetaMask shows an error during signing:
Check network β Ensure you're on Base Sepolia (Chain ID 84532)
Check balance β Ensure you have enough USDC for the request
Debug Mode
Enable the debug log by clicking "Toggle Debug Log" at the bottom of the page. This shows:
In this guide, you learnt what x402 is all about, including its use cases, components, architecture diagram etc. You also learnt how to set up the project and built a complete pay-per-request blockchain data API using x402 and GetBlock.
# Your wallet address to receive payments
PAYMENT_WALLET_ADDRESS=0xYourWalletAddressHere
# GetBlock API key (optional - will use mock data if not set)
GETBLOCK_API_KEY=your_getblock_api_key_here
# Server port
PORT=4021
server/server.js
import express from "express";
import cors from "cors";
import path from "path";
import { fileURLToPath } from "url";
import { paymentMiddleware } from "@x402/express";
import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server";
import { registerExactEvmScheme } from "@x402/evm/exact/server";
import axios from "axios";
import "dotenv/config";
// ES Module directory resolution
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
// =============================================================================
// CORS Configuration
// =============================================================================
// x402 uses custom headers for payment data, so we need to expose them
app.use(
cors({
origin: true,
credentials: true,
methods: ["GET", "POST", "OPTIONS"],
allowedHeaders: [
"Content-Type",
"Authorization",
"X-PAYMENT",
"X-Payment",
"x-payment",
],
exposedHeaders: [
"X-PAYMENT-RESPONSE",
"X-Payment-Response",
"x-payment-response",
"X-PAYMENT-REQUIRED",
"X-Payment-Required",
"x-payment-required",
],
})
);
app.use(express.json());
// =============================================================================
// Request Logging (for debugging)
// =============================================================================
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
const paymentHeader = req.headers["x-payment"] || req.headers["X-PAYMENT"];
if (paymentHeader) {
console.log(" Payment header present (length:", paymentHeader.length, ")");
}
next();
});
// =============================================================================
// Configuration
// =============================================================================
const payTo = process.env.PAYMENT_WALLET_ADDRESS;
const GETBLOCK_API_KEY = process.env.GETBLOCK_API_KEY;
const GETBLOCK_URL = GETBLOCK_API_KEY
? `https://go.getblock.io/${GETBLOCK_API_KEY}`
: null;
console.log("\nπ Configuration:");
console.log(` Payment wallet: ${payTo}`);
console.log(` GetBlock API: ${GETBLOCK_API_KEY ? "Configured" : "Not configured (using mock data)"}`);
// Validate required config
if (!payTo) {
console.error("β Missing PAYMENT_WALLET_ADDRESS in .env");
process.exit(1);
}
// =============================================================================
// GetBlock API Helper
// =============================================================================
async function callGetBlock(method, params = []) {
// If no API key, return mock data for demo purposes
if (!GETBLOCK_URL) {
console.log(" Using mock data (no GetBlock API key)");
if (method === "eth_blockNumber") {
const mockBlock = Math.floor(Date.now() / 1000);
return { result: "0x" + mockBlock.toString(16) };
}
if (method === "eth_gasPrice") {
return { result: "0x" + (20 * 1e9).toString(16) }; // 20 Gwei
}
return { result: null };
}
// Call GetBlock API
try {
const response = await axios.post(GETBLOCK_URL, {
jsonrpc: "2.0",
id: "getblock",
method,
params,
});
return response.data;
} catch (error) {
console.error(" GetBlock API error:", error.message);
throw error;
}
}
// =============================================================================
// x402 Setup
// =============================================================================
// Initialize the facilitator client
// The facilitator verifies payment signatures and settles transactions
const facilitatorUrl = "https://facilitator.payai.network";
console.log(` Facilitator: ${facilitatorUrl}`);
const facilitatorClient = new HTTPFacilitatorClient({
url: facilitatorUrl,
});
// Create the resource server and register the EVM payment scheme
const server = new x402ResourceServer(facilitatorClient);
registerExactEvmScheme(server);
// =============================================================================
// Payment Route Configuration
// =============================================================================
// Define which routes require payment and how much they cost
const paymentConfig = {
"GET /api/eth/block/latest": {
accepts: [
{
scheme: "exact", // Payment scheme (exact amount)
price: "$0.001", // Price in USD
network: "eip155:84532", // Base Sepolia (CAIP-2 format)
payTo, // Your wallet address
},
],
description: "Get latest Ethereum block number",
mimeType: "application/json",
},
"GET /api/eth/gas": {
accepts: [
{
scheme: "exact",
price: "$0.001",
network: "eip155:84532",
payTo,
},
],
description: "Get current gas price",
mimeType: "application/json",
},
};
// Apply the payment middleware
// This intercepts requests to protected routes and verifies payment
app.use(paymentMiddleware(paymentConfig, server));
// =============================================================================
// Static Files & Routes
// =============================================================================
// Serve the frontend
app.use(express.static(__dirname));
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "../client/index.html"));
});
// Free endpoint: API information
app.get("/api", (req, res) => {
res.json({
message: "GetBlock x402 API",
version: "1.0.0",
network: "Base Sepolia (eip155:84532)",
facilitator: facilitatorUrl,
payTo,
endpoints: [
{
path: "/api/eth/block/latest",
price: "$0.001 USDC",
description: "Get latest Ethereum block number",
},
{
path: "/api/eth/gas",
price: "$0.001 USDC",
description: "Get current gas price",
},
],
});
});
// =============================================================================
// Protected Endpoints (require payment)
// =============================================================================
// Get latest block number
app.get("/api/eth/block/latest", async (req, res) => {
console.log(" β Payment verified - serving block data");
try {
const result = await callGetBlock("eth_blockNumber");
res.json({
blockNumber: result.result,
decimal: parseInt(result.result, 16),
timestamp: new Date().toISOString(),
source: GETBLOCK_URL ? "getblock" : "mock",
});
} catch (error) {
console.error(" Error fetching block number:", error.message);
res.status(500).json({ error: error.message });
}
});
// Get current gas price
app.get("/api/eth/gas", async (req, res) => {
console.log(" β Payment verified - serving gas data");
try {
const result = await callGetBlock("eth_gasPrice");
const gasPriceWei = BigInt(result.result);
res.json({
gasPriceWei: result.result,
gasPriceGwei: (Number(gasPriceWei) / 1e9).toFixed(2),
timestamp: new Date().toISOString(),
source: GETBLOCK_URL ? "getblock" : "mock",
});
} catch (error) {
console.error(" Error fetching gas price:", error.message);
res.status(500).json({ error: error.message });
}
});
// =============================================================================
// Error Handling
// =============================================================================
app.use((err, req, res, next) => {
console.error("Server error:", err);
res.status(500).json({ error: err.message });
});
// =============================================================================
// Start Server
// =============================================================================
const PORT = process.env.PORT || 4021;
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
console.log("Protected endpoints:");
Object.entries(paymentConfig).forEach(([route, config]) => {
console.log(` ${route} - ${config.accepts[0].price} USDC`);
})
});