build-x402-server

作成者: 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のその他のスキル

git.repo-manager
coinbase
git.repo-manager — coinbase/cds が公開する、AIエージェント向けのインストール可能なスキルです。
official
agentic-wallet
coinbase
awal CLIによる暗号ウォレット操作 — サインイン、残高確認、USDC/ETH/POL/SOLの送金、トークン取引、ウォレットへの資金追加、x402支払いプロトコルの使用…
official
authenticate-wallet
coinbase
メールOTPベースのウォレット認証、検証、ステータス確認機能。2段階ログインフロー:メールで6桁のOTPを受信し、flowIdとコードで検証して認証を完了。コマンド実行前にシェルインジェクションを防ぐため、メール、flowId、OTPの入力検証ルールを実装。ステータス確認、残高照会、アドレス取得、ウォレットウィンドウアクセスをCLIコマンドで提供。全コマンドは機械可読な--json出力に対応。
official
fund
coinbase
Coinbase Onrampまたは直接送金でウォレットにUSDCを入金します。ユーザーがプリセット金額($10、$20、$50)またはカスタム値を選択し、Apple Pay、デビットカード、銀行振込、Coinbaseアカウントからの資金調達方法を選べる補助UIを開きます。カードとApple Payは即時、ACH銀行振込は1~3日と、決済時間が異なる複数の支払い方法に対応しています。Baseネットワーク上でUSDCとして資金を入金します。また、npx awal@2.0.3...経由でウォレットアドレスに直接USDCを送金することもできます。
official
monetize-service
coinbase
有料APIエンドポイントをデプロイし、他のエージェントがx402プロトコルを介して発見・支払いできるようにします。HTTP 402支払いプロトコルを使用してBase上でリクエストごとにUSDCを請求。クライアントは署名済みトランザクションで支払い、APIキーやアカウントは不要。ディスカバリー拡張を宣言すると、エンドポイントが自動的にx402 Bazaarに登録され、エージェントによる発見が可能に。Expressミドルウェアを使用して、エンドポイントごとに複数の価格帯、ワイルドカードルート、複数の支払いオプションをサポート。@x402/expressおよび@x402/core上に構築...
official
pay-for-service
coinbase
Base上でx402プロトコルを介した自動USDC支払いにより有料APIを呼び出します。x402対応エンドポイントに対してHTTPリクエスト(GET、POSTなど)を実行し、USDC支払いを自動的に処理します。メソッド、JSONボディ、クエリパラメータ、カスタムヘッダーを通じたリクエストのカスタマイズをサポートします。支払い制御機能を含みます:リクエストごとの最大USDC金額の設定、相関IDによる関連操作のグループ化。ウォレット認証と十分なUSDC残高が必要です。シェルインジェクションを防ぐため、すべてのユーザー入力を検証します...
official
query-blockchain-data
coinbase
BaseのCDP SQL APIを介してx402を使用し、オンチェーンのブロックチェーンデータをクエリします。あなたまたはユーザーがデコードされたブロックに関するオンチェーン情報を確認したい場合に使用します。
official
query-onchain-data
coinbase
Base上のオンチェーンデータをSQLでクエリし、クエリごとにx402支払いを行います。CoinbaseQL(ClickHouseベースのSQL方言)を介して、デコードされたイベント、トランザクション、ブロックにアクセス可能。結合、CTE、サブクエリ、標準関数をサポート。利用可能な3つの主要テーブル:base.events(デコードされたスマートコントラクトログ)、base.transactions(完全なトランザクションデータ)、base.blocks(ブロックメタデータ)。イベントクエリでは、インデックス付きフィールド(event_signature、address、block_timestamp)でのフィルタリングが必要で、全テーブルスキャンを回避します。
official