Getting Started

Quickstart

Accept your first x402 payment in under 5 minutes using the live x402 protocol on Base testnet. No account required. No wallet setup needed for testing.

What you'll build A simple API server with one endpoint that requires a $0.001 USDC payment before serving data. An x402-compatible client (or the sandbox tester) will pay it autonomously and receive the response — no human clicks involved.

Prerequisites

You'll need the following before starting:

ℹ️
Using the live x402 protocol This quickstart uses @coinbase/x402 — the open-source facilitator built by Coinbase. The x402 Agentic middleware wrapper (@x402agentic/middleware) is coming Q2 2026 and will simplify this further. The underlying protocol is identical.

Step 1 — Install dependencies

Create a new project and install the x402 facilitator and Express:

bash
# Create project mkdir my-x402-api && cd my-x402-api npm init -y # Install dependencies npm install express @coinbase/x402 viem

Step 2 — Create your server

Create server.js and add the x402 payment middleware to a single route:

server.js
import express from 'express' import { paymentMiddleware, Network, Resource } from '@coinbase/x402/express' const app = express() // ── Your receiving wallet address ────────────────────────────── const MY_WALLET = '0xYourWalletAddressHere' // ── Attach x402 middleware ───────────────────────────────────── // This intercepts requests and returns HTTP 402 if unpaid app.use(paymentMiddleware( MY_WALLET, { "/api/data": new Resource({ price: "$0.001", network: Network.BaseSepolia, // testnet — use Network.Base for mainnet }) }, { network: Network.BaseSepolia } )) // ── Your protected endpoint ──────────────────────────────────── app.get('/api/data', (req, res) => { res.json({ message: '✓ Payment verified — here is your data', timestamp: new Date().toISOString(), paidBy: req.headers['x-payment-response'] ?? 'unknown', }) }) app.listen(3000, () => { console.log('x402 server running → http://localhost:3000') console.log('Try: curl http://localhost:3000/api/data') })
⚠️
Replace the wallet address Swap 0xYourWalletAddressHere with your real Base Sepolia wallet address. This is where USDC payments will land.

Step 3 — Run the server

bash
node server.js # Output: x402 server running → http://localhost:3000 Try: curl http://localhost:3000/api/data

If you curl the endpoint without payment, you'll get the expected 402 response:

bash
curl -i http://localhost:3000/api/data # Response (no payment sent): HTTP/1.1 402 Payment Required X-Payment-Required: true X-Price: 0.001 X-Currency: USDC X-Network: eip155:84532 X-Pay-To: 0xYourWalletAddressHere { "error": "Payment required", "x402Version": 2, "accepts": [{ "scheme": "exact", "network": "eip155:84532", "maxAmountRequired": "1000", "asset": "0x036CbD...", "payTo": "0xYourWallet..." }] }

Step 4 — Make a payment and test

Now test the full payment flow using the Coinbase CDP x402 client. Create a second file, client.js:

client.js
import { wrapFetchWithPayment } from '@coinbase/x402/client' import { createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { baseSepolia } from 'viem/chains' // ── Load your test wallet private key ────────────────────────── // Never commit real private keys — use env vars in production const account = privateKeyToAccount(process.env.PRIVATE_KEY) const walletClient = createWalletClient({ account, chain: baseSepolia, transport: http() }) // ── Wrap fetch — auto-pays any 402 response ──────────────────── const fetchWithPayment = wrapFetchWithPayment(fetch, walletClient) // ── Call the endpoint — payment happens automatically ────────── const res = await fetchWithPayment('http://localhost:3000/api/data') const data = await res.json() console.log('Status:', res.status) // 200 console.log('Data:', data)
bash
# Set your test wallet private key export PRIVATE_KEY=0xYourTestPrivateKeyHere node client.js # Output: Status: 200 Data: { message: '✓ Payment verified — here is your data', timestamp: '2026-03-03T12:00:00.000Z', paidBy: '0xYourTestWallet...' }
It worked! The client automatically detected the 402 response, paid 0.001 USDC on-chain via Base Sepolia, attached the payment proof in an X-Payment header, and retried the request — all in one call. No human clicked anything.

How the payment flow works

Here's exactly what happened under the hood in Step 4:

  1. 1

    Client sends initial request

    fetchWithPayment makes a normal HTTP GET to /api/data. The server has no payment proof, so it returns HTTP 402 with the payment terms in the body.

  2. 2

    Client parses the 402

    The wrapped fetch reads the accepts array from the 402 body — amount (0.001 USDC), network (Base Sepolia), and destination wallet.

  3. 3

    Client pays on-chain

    The wallet client signs and submits a USDC transfer on Base Sepolia. Settlement typically takes 0.3–0.8 seconds. The transaction hash becomes the payment proof.

  4. 4

    Client retries with payment header

    The same request is retried with an X-Payment header containing the signed payment payload. The facilitator verifies the on-chain transaction.

  5. 5

    Server returns HTTP 200

    Payment verified. The server responds with the actual data and an X-Payment-Response header confirming settlement.

Payment Headers Reference

Headers sent by the server in the 402 response and by the client in the retry:

Header Direction Description
X-Payment-Required Server → Client Always true on a 402 response
X-Price Server → Client Amount required, e.g. 0.001
X-Currency Server → Client Token symbol, e.g. USDC
X-Network Server → Client CAIP-2 chain ID, e.g. eip155:8453 (Base mainnet)
X-Pay-To Server → Client Destination wallet address
X-Payment Client → Server Base64-encoded signed payment payload
X-Payment-Response Server → Client Confirmation of verified settlement on 200 response

Going to Mainnet

When you're ready to accept real USDC payments, swap the network from BaseSepolia to Base:

server.js — mainnet diff
// Before (testnet) network: Network.BaseSepolia // After (mainnet) network: Network.Base // That's it. Same code, real USDC.
⚠️
Before going to mainnet Make sure your receiving wallet address is one you fully control and have backed up. Payments land directly on-chain — there's no escrow, no reversal, no intermediary.

Next Steps

Now that you have a working x402 endpoint, here's where to go next: