build-x402-server

tarafından coinbase

Write code that charges for an HTTP route with the x402 protocol and receives USDC in a CDP-managed wallet. Covers TypeScript (Express, Hono, Next.js) and…

npx skills add https://github.com/coinbase/cdp-sdk --skill build-x402-server

Build an x402 server

Take the user from an unprotected HTTP route to one that answers 402 Payment Required and settles a real payment into a CDP-managed wallet.

Resolve the Decisions table below before writing any code, then read only the language and framework subsections you resolved to in step 3.

When not to use this skill

  • The user sells through Coinbase Business. The Business Checkouts API returns one checkout with a hosted payment URL for people and a payable x402_url for agents, and they run no server at all. Check for this early: for that user it is a genuinely better answer than anything below.
  • The user is charging for an MCP tool rather than an HTTP route. See Charge over MCP.
  • The user is the one paying. Use the build-x402-client skill.
  • The user wants a deployed money-making service with no code. Use the agentic-wallet monetize-service skill. It is the nearest neighbour to this skill and the most likely mis-selection.

Decisions

Resolve every row before writing code. Detect first; only ask when detection is ambiguous.

DecisionHow to detectAsk only ifDefault
Languagepackage.json -> TypeScript. pyproject.toml / requirements.txt -> Python.Both present, or neitherAsk
FrameworkRead deps for express, hono, next, fastapi, flask.No server framework presentAsk; suggest Express or FastAPI
Wiring approachAn existing x402ResourceServer or paymentMiddleware call -> facilitator swap.Greenfield
Route config sourceAn x402.config.json already in the project -> config file.Inline in code
Receiver walletThe user supplied a payTo address -> use it.CDP-provisioned wallet
Networkenvironment: "development" selects testnets.Never assume mainnetdevelopment
SchemeFixed price -> exact. Metered or usage-based -> upto.exact

Two hard rules, not preferences:

  1. Never move a server to mainnet unless the user asks in the current turn. That puts real payers in front of a route that may not be ready.
  2. If the user supplies a payTo address, echo it back for confirmation before writing it. A typo'd receiver sends every future payment somewhere unrecoverable.

Steps

1. Confirm credentials

Before installing anything, check the environment for CDP_API_KEY_ID, CDP_API_KEY_SECRET, and CDP_WALLET_SECRET. The API key authenticates the server to the CDP Facilitator; the wallet secret provisions the wallet that receives payments, and is only needed when the user has not supplied a payTo of their own. Send them to API key authentication if they have no key. Also confirm the runtime: Node.js 22 or later, or Python 3.10 or later.

2. Install

Pick the line matching the Decisions table. @x402/core, @x402/evm, @x402/svm, and @x402/extensions are optional peer dependencies of the CDP SDK, so they are not installed for you, and all four are needed even for an EVM-only server because @coinbase/cdp-sdk/x402 imports them at module load. Only the framework and its adapter change between the three TypeScript lines.

# TypeScript, Express
npm install express @coinbase/cdp-sdk @x402/core @x402/evm @x402/svm @x402/extensions @x402/express

# ...or Hono:    hono @hono/node-server, and @x402/hono in place of @x402/express
# ...or Next.js: next, and @x402/next in place of @x402/express

# Python
pip install "cdp-sdk" "x402[evm,svm,fastapi]" uvicorn   # FastAPI
pip install "cdp-sdk" "x402[evm,svm,flask]"             # Flask

3. Price the route

Three things are needed from the user before writing anything. Ask for whichever cannot be inferred: which routes to charge for, the price per call, and a one-line description of what each route returns. The description is not decoration — it is what buyers see when the service is listed for discovery, so a vague one costs the user customers later.

State the containment rule plainly: only routes named in the config are protected, everything else stays free. That is the sentence that stops someone paywalling /health.

Read only the subsections matching the language and framework resolved above.

TypeScript

createX402Server provisions the receiver wallet, wires the CDP Facilitator, registers the schemes and extensions, and returns an object any x402 framework adapter accepts. It is async — await it before app.use.

import { createX402Server } from "@coinbase/cdp-sdk/x402";
import { paymentMiddlewareFromHTTPServer } from "@x402/express";
import express from "express";

const app = express();

const server = await createX402Server({
  environment: "development", // testnets and test funds
  routes: {
    "GET /report": { price: "$0.01", description: "Generate a concise research report" },
  },
});

app.use(paymentMiddlewareFromHTTPServer(server));
app.get("/report", (_req, res) => res.json({ report: "..." }));

app.listen(8402, () => console.log(`Receiving payments at ${server.payToEvmAddress}`));

Two variants on that shape:

  • The user already runs x402. Do not rewrite their server. Replace the facilitator argument with createCdpFacilitatorClient() from @coinbase/cdp-sdk/x402 — same return type, so nothing else in their code moves. This path needs a payTo address, and the factory is synchronous.
  • Routes belong in a file. Pass configPath: "./x402.config.json" instead of routes. Inline routes win per key when both are given, which is how you keep a shared file and still special-case one route in code. Keep credentials in environment variables, not the file.

Hono is the Express code with @x402/hono in place of @x402/express and serve({ fetch: app.fetch, port }) in place of app.listen. The server object is identical.

Next.js is the one genuine exception. App Router route files re-evaluate, so build the server once in its own module and import it from the handler:

// app/x402.ts — note the /server subpath: the client ExactEvmScheme needs a signer
import { x402ResourceServer } from "@x402/core/server";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { createCdpFacilitatorClient } from "@coinbase/cdp-sdk/x402";

export const server = new x402ResourceServer(createCdpFacilitatorClient()).register(
  "eip155:84532",
  new ExactEvmScheme(),
);

// app/api/report/route.ts
import { withX402 } from "@x402/next";
export const GET = withX402(handler, { accepts: [...], description: "..." }, server);

Gotchas worth stating once:

  • Register the middleware before the protected handlers.
  • Omitting environment means mainnet.
  • Under "development", routes default to both Base Sepolia and Solana Devnet.

Usage-based pricing (upto) only when the user asks for it. The route takes scheme: "upto" and a price that acts as a ceiling; the handler calls setSettlementOverrides(res, { amount }) with the amount actually used before sending the body. amount is a string, and it accepts atomic units ("100000" is $0.10 in 6-decimal USDC), a dollar price ("$0.05"), or a percentage of the authorized ceiling ("50%") — pick whichever the usage calculation produces naturally. upto is EVM-only, so under "development" it resolves to Base Sepolia alone.

Python

There is no createX402Server in Python, so assemble the pieces by hand. It is two halves, and naming them is what keeps the Python version from reading as long and arbitrary:

  1. A CDP wallet to receive payments, resolved from cdp.evm.get_or_create_account(...).address.
  2. The x402 Foundation middleware, pointed at the CDP Facilitator with create_facilitator_config().
from cdp.x402 import create_facilitator_config
from fastapi import FastAPI
from x402.http import HTTPFacilitatorClient, PaymentOption
from x402.http.middleware.fastapi import PaymentMiddlewareASGI
from x402.http.types import RouteConfig
from x402.mechanisms.evm.exact import ExactEvmServerScheme
from x402.server import x402ResourceServer

PAY_TO = "0x1234567890123456789012345678901234567890"  # Your EVM address to get paid on Base Sepolia
NETWORK = "eip155:84532"  # Base Sepolia

server = x402ResourceServer(HTTPFacilitatorClient(create_facilitator_config()))
server.register(NETWORK, ExactEvmServerScheme())

routes = {
    "GET /report": RouteConfig(
        accepts=[PaymentOption(scheme="exact", pay_to=PAY_TO, price="$0.01", network=NETWORK)],
        mime_type="application/json",
        description="AI-generated report",
    ),
}

app = FastAPI()
app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server)

Run it with uvicorn.run(app, port=8402).

The sharpest edge is resolving PAY_TO. CdpClient is an async context manager, but the route config above is module-level and synchronous, which is why the examples resolve the receiver once at import time with asyncio.run(resolve_pay_to()). That works when the module is the entry point. Under an ASGI server that imports it from inside a running event loop, it raises RuntimeError, and the user needs a lifespan hook instead.

Flask is the same code with three substitutions: x402ResourceServerSync and HTTPFacilitatorClientSync in place of the async pair, and payment_middleware(app, routes=routes, server=server) from x402.http.middleware.flask, which is a function that mutates the app rather than a middleware class. Handing Flask the async x402ResourceServer raises a TypeError.

Two more gotchas: PaymentOption is a dataclass whose scheme, pay_to, price, and network have no defaults, so a missing one is a TypeError at construction — which, with a module-level route map, means the server refuses to import rather than failing a request later. And this path is EVM-only, with no Solana option.

4. Confirm the route is protected

Start the server, then from a second terminal:

curl -i http://localhost:8402/report
HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJQYXltZW50IHJlcXVpcmVkIiwi...

This is the cheap checkpoint before any money moves, and it needs no buyer. Do not skip to step 5.

5. Take a real payment

Either testing path works:

  • Point the agentic-wallet pay-for-service skill at http://localhost:8402/report.
  • Build a buyer with the build-x402-client skill and point it at the same URL.

Success is HTTP 200 on the buyer side. The buyer wallet needs testnet USDC first, which is step 4 of the client skill — link it rather than re-teaching funding here.

Troubleshooting

SymptomCauseFix
Route returns 200 with no paymentRoute key does not match the real method and path, or middleware was registered after the handlerCompare the key to the handler; move app.use above it
402 with no PAYMENT-REQUIRED headerThe middleware was never reachedCheck registration order and the mount path
Verification passes, settlement failsBuyer and server are on different chainsMatch the buyer's network to the one in the 402
Auth error at startupCDP_API_KEY_* not visible to the processCheck how the process loads its environment, not just .env
Payments land somewhere unknownA CDP wallet was provisioned and the printed payTo was never recordedRead it back from server.payToEvmAddress and save it

Runnable examples

TypeScript, under https://github.com/coinbase/cdp-sdk/blob/main/examples/typescript/x402/servers/: express/server.ts (all three approaches), express/x402.config.json and express/x402.config.schema.json, hono/server.ts, next/app/api/report/route.ts, mcp/server.ts.

Python, under https://github.com/coinbase/cdp-sdk/blob/main/examples/python/x402/servers/: fastapi/server.py, flask/server.py, bazaar.py, mcp/server.py.

After the first payment

  • Make the endpoint findable: Get discovered. TypeScript's createX402Server handles it automatically; Python needs manual metadata like bazaar.py above
  • What settled the payment: CDP Facilitator
  • Other networks, schemes, receivers, lifecycle hooks: Production configuration
  • Charging for MCP tools: Charge over MCP
  • Mainnet: drop environment: "development" and confirm with the user first

coinbase tarafından daha fazla skill

git.repo-manager
coinbase
git.repo-manager — coinbase/cds tarafından yayınlanan, yapay zeka ajanları için kurulabilir bir beceri.
official
agentic-wallet
coinbase
Awal CLI üzerinden kripto cüzdan işlemleri — giriş yapma, bakiyeleri kontrol etme, USDC/ETH/POL/SOL gönderme, token takası yapma, cüzdanı fonlama ve x402 ödeme protokolünü kullanarak…
official
authenticate-wallet
coinbase
E-posta OTP tabanlı cüzdan kimlik doğrulaması, doğrulama ve durum kontrolü ile birlikte. İki adımlı giriş akışı: e-posta ile başlatılarak 6 haneli OTP alınır, ardından flowId ve kod ile doğrulanarak kimlik doğrulama tamamlanır. Komutları çalıştırmadan önce shell enjeksiyonunu önlemek için e-posta, flowId ve OTP için giriş doğrulama kuralları içerir. Yardımcı CLI komutları aracılığıyla durum kontrolü, bakiye sorgulama, adres alma ve cüzdan penceresi erişimi sağlar. Tüm komutlar makine tarafından okunabilir çıktı için --json çıktısını destekler...
official
fund
coinbase
Coinbase Onramp veya doğrudan transfer yoluyla cüzdana USDC yatırır. Kullanıcıların önceden belirlenmiş miktarları (10$, 20$, 50$) veya özel değerleri seçip Apple Pay, banka kartı, banka havalesi veya Coinbase hesabı finansmanı arasından tercih yapabileceği bir yardımcı arayüz açar. Farklı mutabakat sürelerine sahip birden fazla ödeme yöntemini destekler: kart ve Apple Pay için anlık, ACH banka havaleleri için 1–3 gün. Fonları Base ağında USDC olarak yatırır; alternatif olarak, kullanıcılar npx awal@2.0.3... aracılığıyla doğrudan cüzdan adresine USDC göndere
official
monetize-service
coinbase
Ücretli bir API uç noktası dağıtır; diğer ajanlar bunu x402 protokolü üzerinden keşfedip ödeme yapabilir. Base üzerinde HTTP 402 ödeme protokolü kullanarak istek başına USDC tahsil eder; istemciler imzalı işlemlerle ödeme yapar, API anahtarı veya hesap gerekmez. Keşif uzantılarını bildirdiğinizde uç noktaları otomatik olarak x402 Bazaar'a kaydeder. Express middleware kullanarak uç nokta başına birden fazla fiyatlandırma katmanı, joker karakter rotaları ve birden fazla ödeme seçeneğini destekler. @x402/express ve @x402/core üzerine inşa edilmiştir...
official
pay-for-service
coinbase
Base üzerinde x402 protokolü aracılığıyla otomatik USDC ödemesiyle ücretli API'leri çağırır. Atomik USDC ödemeleriyle x402 destekli uç noktalara HTTP istekleri (GET, POST vb.) yürütür. Yöntem, JSON gövdesi, sorgu parametreleri ve özel başlıklar aracılığıyla istek özelleştirmesini destekler. Ödeme kontrolleri içerir: istek başına maksimum USDC miktarını ayarlayın ve ilgili işlemleri korelasyon kimlikleriyle gruplandırın. Cüzdan kimlik doğrulaması ve yeterli USDC bakiyesi gerektirir; shell'i önlemek için tüm kullanıcı girdilerini doğrular...
official
query-blockchain-data
coinbase
Base üzerinde CDP SQL API aracılığıyla x402 kullanarak zincir üstü blok zinciri verilerini sorgulayın. Siz veya kullanıcınız, çözülmüş bloklar hakkında zincir üstü bilgileri görüntülemek istediğinizde kullanın.
official
query-onchain-data
coinbase
Base üzerinde SQL kullanarak sorgu başına x402 ödemeleriyle zincir üstü verileri sorgulayın. CoinbaseQL aracılığıyla çözülmüş olaylara, işlemlere ve bloklara erişin; bu, birleştirmeleri, CTE'leri, alt sorguları ve standart işlevleri destekleyen ClickHouse tabanlı bir SQL lehçesidir. Üç ana tablo mevcuttur: base.events (çözülmüş akıllı sözleşme günlükleri), base.transactions (tam işlem verileri) ve base.blocks (blok meta verileri). Olay sorgularında tam tablo taramasını önlemek için indekslenmiş alanlarda (event_signature, address, block_timestamp) filtreleme gerektirir.
official