Feb 15, 2026

x402 Thirdweb Demo: SBC on Base

x402 Thirdweb Demo: SBC on Base

Ever wanted to monetize an API with crypto — no subscriptions, no API keys, no payment forms? The x402 protocol makes it possible by turning the HTTP 402 Payment Required status code into an actual payment flow, and thirdweb's hosted facilitator handles the on-chain settlement.

In this guide, you'll build a working demo that accepts SBC (Stable Coin) payments on Base for API access. By the end, you'll have a server that charges 0.01 SBC per request, a CLI client that pays automatically, and a web UI where users can connect their wallet and pay in the browser.

What You'll Build

The demo has three parts:

  • An Express server with a paid /paid endpoint that charges 0.01 SBC per request

  • A CLI client that automatically detects the 402 response, signs an ERC-2612 permit, and retries with payment

  • A React web app where users connect their wallet and pay through the browser

Here's the payment flow at a glance:

Client                    Server                   Thirdweb Facilitator       Base
  |                         |                              |                    |
  |--- GET /paid ---------->|                              |                    |
  |<-- 402 + requirements --|                              |                    |
  |                         |                              |                    |
  | [sign ERC-2612 permit]

Client                    Server                   Thirdweb Facilitator       Base
  |                         |                              |                    |
  |--- GET /paid ---------->|                              |                    |
  |<-- 402 + requirements --|                              |                    |
  |                         |                              |                    |
  | [sign ERC-2612 permit]

Client                    Server                   Thirdweb Facilitator       Base
  |                         |                              |                    |
  |--- GET /paid ---------->|                              |                    |
  |<-- 402 + requirements --|                              |                    |
  |                         |                              |                    |
  | [sign ERC-2612 permit]

Client                    Server                   Thirdweb Facilitator       Base
  |                         |                              |                    |
  |--- GET /paid ---------->|                              |                    |
  |<-- 402 + requirements --|                              |                    |
  |                         |                              |                    |
  | [sign ERC-2612 permit]

Prerequisites

Before you begin, make sure you have:

  • Node.js 18+ installed

  • A GitHub account to clone the repo

  • A MetaMask or other browser wallet (for the web demo)

  • Some SBC tokens on Base for testing payments (the payer wallet needs them)

Step 1: Set Up Your Thirdweb Account and API Keys

Everything in this demo authenticates through thirdweb, so you'll need to create an account and generate your keys first.

1.1 Login or Create a Thirdweb Account

  1. Go to thirdweb.com and click Login.

  2. Connect your wallet (MetaMask, Coinbase Wallet, etc.) — you'll be prompted to sign a message to verify ownership.

  3. Once signed in, you'll land on the thirdweb team overview dashboard.

1.2 Create a Project and API Key (Client ID + Secret Key)

The API key gives your app access to thirdweb's infrastructure — RPCs, the x402 facilitator, and more.

  1. Click Create Project.

  2. Give your key a descriptive name (e.g., x402-sbc-demo).

  3. For development, you can leave domain restrictions empty. In production, restrict to your domain.

  4. Click Create to generate the key.

  5. You'll see two values — save both immediately:

    • Client ID — used in frontend code (safe to expose publicly)

    • Secret Key — used in backend code (keep this private)

  6. Check the confirmation box and click Complete.

Important: The Secret Key is only shown once. If you lose it, you'll need to create a new API key.

Step 2: Server Wallet Details

To configure the environment varitibles in Step 4, we'll need the server wallet's address, private key, and vault access token.

The server wallet is what thirdweb uses to sign and settle payments on-chain on your behalf. It's secured through thirdweb's Vault — their non-custodial key management system.

Navigate to the Server Wallets view to locate the information.

2.1 Get the server wallet address

  1. Locate the wallet address.

2.2 Get the Vault Access Token

The Vault Access Token allows your backend to sign transactions using the server wallet.

  1. Navigate to the Configuration tab.

  2. Click Rotate Admin Key. This will generate new vault keys. Securely save the new vault admin key and new vault acccess token.

  3. The vault access token will start with vt_act_...save this immediately.

Step 3: Clone the Repository and Install Dependencies

Now that your thirdweb credentials are ready, let's set up the code.

git clone https://github.com/stablecoinxyz/x402-thirdweb-demo-base.git
cd x402-thirdweb-demo-base
npm

git clone https://github.com/stablecoinxyz/x402-thirdweb-demo-base.git
cd x402-thirdweb-demo-base
npm

git clone https://github.com/stablecoinxyz/x402-thirdweb-demo-base.git
cd x402-thirdweb-demo-base
npm

git clone https://github.com/stablecoinxyz/x402-thirdweb-demo-base.git
cd x402-thirdweb-demo-base
npm

For the web demo, install its dependencies separately:

cd web
npm install
cd

cd web
npm install
cd

cd web
npm install
cd

cd web
npm install
cd

Step 4: Configure Your Environment Variables

Copy the example environment file and fill in your values:

cp
cp
cp
cp

Open .env in your editor and fill in each field:

# Your thirdweb Secret Key
# Get this from https://thirdweb.com/dashboard/settings/api-keys
THIRDWEB_SECRET_KEY=your-secret-key-here

# Your server wallet address
# Create at https://thirdweb.com/dashboard/engine
SERVER_WALLET_ADDRESS=0xYourServerWalletAddress

# Vault Access Token for server wallet signing
VAULT_ACCESS_TOKEN=vt_act_your-token-here

# Where payments are sent (defaults to SERVER_WALLET_ADDRESS if not set)
MERCHANT_ADDRESS=0xYourMerchantAddress

# Private key of the wallet that will pay (only needed for the CLI client demo)
CLIENT_PRIVATE_KEY=0xYourClientPrivateKey

# Public thirdweb Client ID (only needed for the web demo)
# This is the Client ID, NOT the Secret Key
VITE_THIRDWEB_CLIENT_ID

# Your thirdweb Secret Key
# Get this from https://thirdweb.com/dashboard/settings/api-keys
THIRDWEB_SECRET_KEY=your-secret-key-here

# Your server wallet address
# Create at https://thirdweb.com/dashboard/engine
SERVER_WALLET_ADDRESS=0xYourServerWalletAddress

# Vault Access Token for server wallet signing
VAULT_ACCESS_TOKEN=vt_act_your-token-here

# Where payments are sent (defaults to SERVER_WALLET_ADDRESS if not set)
MERCHANT_ADDRESS=0xYourMerchantAddress

# Private key of the wallet that will pay (only needed for the CLI client demo)
CLIENT_PRIVATE_KEY=0xYourClientPrivateKey

# Public thirdweb Client ID (only needed for the web demo)
# This is the Client ID, NOT the Secret Key
VITE_THIRDWEB_CLIENT_ID

# Your thirdweb Secret Key
# Get this from https://thirdweb.com/dashboard/settings/api-keys
THIRDWEB_SECRET_KEY=your-secret-key-here

# Your server wallet address
# Create at https://thirdweb.com/dashboard/engine
SERVER_WALLET_ADDRESS=0xYourServerWalletAddress

# Vault Access Token for server wallet signing
VAULT_ACCESS_TOKEN=vt_act_your-token-here

# Where payments are sent (defaults to SERVER_WALLET_ADDRESS if not set)
MERCHANT_ADDRESS=0xYourMerchantAddress

# Private key of the wallet that will pay (only needed for the CLI client demo)
CLIENT_PRIVATE_KEY=0xYourClientPrivateKey

# Public thirdweb Client ID (only needed for the web demo)
# This is the Client ID, NOT the Secret Key
VITE_THIRDWEB_CLIENT_ID

# Your thirdweb Secret Key
# Get this from https://thirdweb.com/dashboard/settings/api-keys
THIRDWEB_SECRET_KEY=your-secret-key-here

# Your server wallet address
# Create at https://thirdweb.com/dashboard/engine
SERVER_WALLET_ADDRESS=0xYourServerWalletAddress

# Vault Access Token for server wallet signing
VAULT_ACCESS_TOKEN=vt_act_your-token-here

# Where payments are sent (defaults to SERVER_WALLET_ADDRESS if not set)
MERCHANT_ADDRESS=0xYourMerchantAddress

# Private key of the wallet that will pay (only needed for the CLI client demo)
CLIENT_PRIVATE_KEY=0xYourClientPrivateKey

# Public thirdweb Client ID (only needed for the web demo)
# This is the Client ID, NOT the Secret Key
VITE_THIRDWEB_CLIENT_ID

Security note: The CLIENT_PRIVATE_KEY is only for the CLI demo. In production, users sign transactions with their own wallets. Never share or commit private keys.

Step 5: Verify SBC Support with the Probe

Before running anything, check that thirdweb's facilitator actually supports SBC on Base:

npm
npm
npm
npm

You should see output confirming that SBC is recognized, with details like:

  • primaryType: "Permit" — confirms ERC-2612 support

  • decimals: 18 — matches SBC's configuration

  • Chain: Base (8453)

If the probe returns an error, double-check that your THIRDWEB_SECRET_KEY is set correctly in .env.

Screenshot: Successful probe output in the terminal

Step 6: Run the Server

Start the Express server:

npm
npm
npm
npm

The server starts on port 3002 with two endpoints:

Endpoint

Cost

Description

GET /

Free

Health check — always returns 200

GET /paid

0.01 SBC

Premium content — returns 402 if no payment

Under the hood, the server uses settlePayment() from the thirdweb SDK. When a request hits /paid:

  1. No payment header? Returns 402 with payment requirements (token address, amount, chain)

  2. Has payment header? Forwards the signed permit to the thirdweb facilitator

  3. Settlement confirmed? Returns the premium content

The price is configured as a custom ERC-20 token amount in src/constants.ts:

price: {
  amount: "10000000000000000", // 0.01 SBC (18 decimals)
  asset: {
    address: "0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798",
    decimals: 18,
    eip712: {
      name: "Stable Coin",
      version: "1",
      primaryType: "Permit",
    },
  },
}
price: {
  amount: "10000000000000000", // 0.01 SBC (18 decimals)
  asset: {
    address: "0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798",
    decimals: 18,
    eip712: {
      name: "Stable Coin",
      version: "1",
      primaryType: "Permit",
    },
  },
}
price: {
  amount: "10000000000000000", // 0.01 SBC (18 decimals)
  asset: {
    address: "0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798",
    decimals: 18,
    eip712: {
      name: "Stable Coin",
      version: "1",
      primaryType: "Permit",
    },
  },
}
price: {
  amount: "10000000000000000", // 0.01 SBC (18 decimals)
  asset: {
    address: "0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798",
    decimals: 18,
    eip712: {
      name: "Stable Coin",
      version: "1",
      primaryType: "Permit",
    },
  },
}

Step 7: Make a Paid Request with the CLI Client

Open a second terminal and run the client:

npm
npm
npm
npm

The client demonstrates the full payment flow:

  1. Hits the free endpoint (GET /) — works normally, returns 200

  2. Hits the paid endpoint (GET /paid) — receives a 402 response

  3. Parses the payment requirements from the 402 response

  4. Signs an ERC-2612 permit using the CLIENT_PRIVATE_KEY

  5. Retries the request with the signed payment in the header

  6. Receives the premium content with a 200 response

This is powered by wrapFetchWithPayment() from the thirdweb SDK, which wraps the standard fetch API to automatically handle the 402 flow.

Step 8: Run the Web Demo

For a visual, browser-based experience:

Terminal 1 (if the server isn't already running):

npm
npm
npm
npm

Terminal 2:

cd web
npm

cd web
npm

cd web
npm

cd web
npm

Open http://localhost:5173 in your browser.

Using the Web Demo

  1. Click Connect Wallet — choose MetaMask, Coinbase Wallet, or any supported wallet on Base.

  2. Make sure your wallet is connected to the Base network.

  3. Click Get Premium Content.

  4. Your wallet will prompt you to sign an ERC-2612 permit (this is not a transaction — it's a gasless signature).

  5. The app retries the request with the signed permit, and you'll see the premium content along with the settlement receipt.

The web app uses thirdweb's useFetchWithPayment() React hook, which handles the entire 402 detection, signing, and retry flow in the browser.

SBC Token Reference

Property

Value

Name

Stable Coin

Symbol

SBC

Contract Address

0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798

Chain

Base (Chain ID: 8453)

Decimals

18

Permit Standard

ERC-2612 (EIP-712 domain version "1")

Backing

1:1 USD, full reserve

Basescan

View on Basescan

Adapting This for Your Own Token

Any ERC-20 token that supports ERC-2612 permit() will work with x402. To swap in your own token, update the values in src/constants.ts:

export const SBC_TOKEN_ADDRESS = "0xYourTokenAddress";
export const SBC_DECIMALS = 18; // your token's decimals
export const SBC_TOKEN_NAME = "Your Token Name"; // must match the on-chain EIP-712 domain name
export const SBC_TOKEN_ADDRESS = "0xYourTokenAddress";
export const SBC_DECIMALS = 18; // your token's decimals
export const SBC_TOKEN_NAME = "Your Token Name"; // must match the on-chain EIP-712 domain name
export const SBC_TOKEN_ADDRESS = "0xYourTokenAddress";
export const SBC_DECIMALS = 18; // your token's decimals
export const SBC_TOKEN_NAME = "Your Token Name"; // must match the on-chain EIP-712 domain name
export const SBC_TOKEN_ADDRESS = "0xYourTokenAddress";
export const SBC_DECIMALS = 18; // your token's decimals
export const SBC_TOKEN_NAME = "Your Token Name"; // must match the on-chain EIP-712 domain name

To find your token's EIP-712 domain name, query the contract directly:

cast call 0xYourTokenAddress "name()(string)" --rpc-url
cast call 0xYourTokenAddress "name()(string)" --rpc-url
cast call 0xYourTokenAddress "name()(string)" --rpc-url
cast call 0xYourTokenAddress "name()(string)" --rpc-url

Troubleshooting

Probe fails with authentication error: Double-check your THIRDWEB_SECRET_KEY in .env. Make sure you're using the Secret Key, not the Client ID.

Client gets a 402 but doesn't retry: Ensure CLIENT_PRIVATE_KEY is set and the wallet has SBC tokens on Base.

Web app can't connect wallet: Make sure VITE_THIRDWEB_CLIENT_ID is set in your .env file (note the VITE_ prefix — Vite requires this to expose the variable to the frontend).

Settlement fails: Check that your server wallet has been properly created in the thirdweb dashboard and the VAULT_ACCESS_TOKEN is valid.

What's Next

Now that you have a working x402 payment flow, you can:

  • Set custom prices by adjusting the amount in src/constants.ts

  • Protect multiple endpoints by applying the payment middleware to any route

  • Build a production API using the same pattern with your own Express/Next.js/Hono backend

  • Deploy the web app with cd web && npm run build — the built files are served automatically by the Express server

Resources

Join the SBC

ecosystem

Partner with us to build a global, free, and accessible payment network.

Join the SBC

ecosystem

Partner with us to build a global, free, and accessible payment network.

Join the SBC

ecosystem

Partner with us to build a global, free, and accessible payment network.

Join the SBC

ecosystem

Partner with us to build a global, free, and accessible payment network.