Skip to main content
This guide walks you through building a payment-gated API using the Key0 SDK. By the end, you have an Express server that accepts USDC payments on Base and issues JWT access tokens to paying agents.
Never use mainnet for testing. Start on Base Sepolia (testnet) where USDC is free. Switch to mainnet only when you are ready to accept real payments.
1

Install the SDK

Install the Key0 SDK and its peer dependencies:
If you use npm or pnpm, replace bun add with npm install or pnpm add.
2

Get a wallet

You need an Ethereum wallet address on Base to receive USDC payments. Any wallet works — MetaMask, Coinbase Wallet, a hardware wallet, or a programmatically generated address.For testnet development, get free test USDC from the Circle faucet (select Base Sepolia).Copy your wallet address. You use it as WALLET_ADDRESS in your configuration.
3

Define plans

Plans describe what you sell and how much it costs. Each plan has a planId, a unitAmount (in USD), and an optional description.
The unitAmount is a string with a dollar sign prefix. The SDK parses it into the correct USDC micro-units on-chain.
4

Implement fetchResourceCredentials

After a payment is verified on-chain, the SDK calls your fetchResourceCredentials callback to issue a credential. This is where you mint a JWT, generate an API key, or call another service.
ACCESS_TOKEN_SECRET must be at least 32 characters. Use a cryptographically random string. Generate one with openssl rand -base64 48.
5

Set up storage

The SDK needs two stores: a ChallengeStore for tracking payment state machines and a SeenTxStore for preventing double-spend attacks. Both use Redis in production.
Both stores accept an optional keyPrefix (default: "key0") if you share a Redis instance with other services.
6

Mount the router

Wire everything together with key0Router. This creates an Express router that serves the A2A agent card and the unified x402 HTTP payment endpoint.
This mounts the following routes automatically:
7

Protect your routes

Use validateAccessToken middleware to protect any route behind a paid JWT. After an agent pays and receives a token, it includes the token as a Bearer header in subsequent requests.
The middleware rejects requests with missing, expired, or invalid tokens and returns the appropriate HTTP error.
8

Set environment variables

Create a .env file in your project root:
If you use Bun, environment variables load automatically from .env. For Node.js, use dotenv or pass them via your process manager.
9

Test it

Start your server and test with curl.Discover the agent card:
Request access (triggers a 402 challenge):
You receive a 402 Payment Required response with the payment requirements: wallet address, chain ID, USDC amount, and a challengeId. An x402-compatible agent uses this information to pay on-chain, then replays the request with a PAYMENT-SIGNATURE header to receive the access grant.
10

Set up refunds

The refund cron is optional but recommended. Without it, payments that fail during credential issuance remain in the PAID state permanently.
The processRefunds function scans for PAID records that were never delivered and refunds them on-chain. Run it on a schedule using BullMQ, node-cron, or any job scheduler.
The refund function uses atomic state transitions internally. Concurrent cron runs across multiple instances do not double-refund. If you use a gas wallet for settlement, pass gasWalletPrivateKey and optionally redis for distributed locking.
11

Go to mainnet

When you are ready to accept real USDC payments, make three changes:
  1. Switch the network from "testnet" to "mainnet" in your SellerConfig and X402Adapter.
  2. Use your production wallet — the address that receives real USDC on Base (chain ID 8453).
  3. Update the refund cron network to "mainnet".
No other code changes are needed. The SDK handles the different chain IDs, USDC contract addresses, and RPC endpoints automatically.

Full working example

Here is the complete seller in a single file:
seller.ts
Run it:

Pay-Per-Request Alternative

The subscription model above issues a JWT that the buyer uses for subsequent requests. If you want to charge for each individual API call instead (no long-lived token), define top-level routes and gate them with key0.payPerRequest() middleware:
Subscription plans and pay-per-call routes can coexist on the same server. Use the ppr-embedded example for a runnable walkthrough.

Next Steps

Pay-Per-Request (Embedded)

Full example with per-request weather and joke routes.

Paying for Access

Show your clients how to discover, pay, and call your API — the buyer’s perspective.

Production Checklist

Security, networking, settlement, and observability — everything before going live.

SellerConfig Reference

Complete reference for every configuration option.