How to setup x402-enabled endpoints with Monad support
This guide shows how to setup payable endpoints using x402 payments and Thirdweb’s built‑in facilitator. It works on Monad testnet/mainnet.
What is x402?
x402 is the HTTP 402 "Payment Required" status code reborn as a minimal protocol for internet‑native micropayments.
Instead of subscriptions or paywalls that require accounts, x402 lets any HTTP endpoint become instantly payable:
- Client requests a resource
- Server responds 402 with a small JSON payment requirement
- Client pays with a signed transaction
- Server verifies and serves the content
Beyond legacy limitations
x402 is designed for a modern internet economy, solving key limitations of legacy systems:
- Reduce fees and friction: Direct onchain payments without intermediaries, high fees, or manual setup.
- Micropayments & usage-based billing: Charge per call or feature with simple, programmable pay-as-you-go flows.
- Machine-to-machine transactions: Let AI agents pay and access services autonomously with no keys or human input needed.
Why x402 on Monad?
Monad is a fully EVM‑compatible Layer 1 with:
- 10,000 TPS
- ~0.4s block times
- Single‑slot finality
- Parallel execution
- Extremely low fees
These properties make Monad an ideal environment for true micropayments and agent-to-agent commerce. Payments settle instantly at low cost and avoid mempool congestion, perfect when many AI agents pay per API call.
Core Flow (Direct Payment)
Facilitator Flow (Recommended for Production)
A facilitator service is optional but recommended in production. Facilitators can batch transactions, cover gas, handle refunds, and simplify client logic.
Building an x402-based app using Thirdweb facilitator
Prerequisites
- Node.js 18+
- An EVM wallet
- Access to Monad testnet funds (USDC test tokens below)
How to get USDC tokens on Monad testnet
You can get USDC tokens for Monad testnet using Circle's faucet:
- Visit https://faucet.circle.com
- Select USDC as the token
- Select Monad Testnet from the Network dropdown
- Enter your wallet address
- Click Send 1 USDC
Limit: One request per (stablecoin, testnet) pair every 2 hours

You'll also need testnet MON tokens for gas fees. Get them from the Monad faucet.
Step 1: Initialize a Next.js App
Create a new Next.js project:
npx create-next-app@latest my-x402-appWhen prompted, select the following options:
- ✅ TypeScript
- ✅ ESLint
- ✅ Tailwind CSS
- ✅
src/directory - ✅ App Router
- ✅ Customize default import alias:
@/*(default)
Navigate to your project:
cd my-x402-appInstall the Thirdweb SDK:
npm install thirdwebCreate a .env.local file for your environment variables:
touch .env.localStep 2: Create a Thirdweb account and get your API keys
-
Sign in (wallet or email/Google)
-
Create a project
-
Go to Settings → API Keys
-
You'll see two keys:
- Client ID (for frontend)
- Secret Key (for backend - keep this secure!)
-
Add to your
.env.localfile:NEXT_PUBLIC_CLIENT_ID=your_client_id_hereSECRET_KEY=your_secret_key_here
Step 3: Create a server wallet
A server wallet can be used to receive payments and interact with the blockchain from your backend.
-
In your Thirdweb dashboard, expand Wallets in the sidebar and click Server Wallets
-
Click the Wallets tab
-
You should already see a Project Wallet by default, or you can create a new one
-
Copy the wallet address and add it to your
.env.localfile asSERVER_WALLETNEXT_PUBLIC_CLIENT_ID=your_client_id_hereSECRET_KEY=your_secret_key_hereSERVER_WALLET=0xYourWallet
Step 4: Create a server side payable endpoint
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051import { NextResponse } from "next/server";import { createThirdwebClient } from "thirdweb";import { facilitator, settlePayment } from "thirdweb/x402";import { monadTestnet } from "thirdweb/chains";const client = createThirdwebClient({ secretKey: process.env.SECRET_KEY });const thirdwebX402Facilitator = facilitator({client,serverWalletAddress: process.env.SERVER_WALLET,});export async function GET(request: Request) {try {const paymentData = request.headers.get("x-payment");const result = await settlePayment({resourceUrl: "http://localhost:3000/api/premium", // change to your production URLmethod: "GET",paymentData,network: monadTestnet, // payable on monad testnetprice: "$0.0001", // Amount per requestpayTo: process.env.SERVER_WALLET!, // payment receiverfacilitator: thirdwebX402Facilitator,});if (result.status === 200) {// If payment is settled, return paid responsereturn NextResponse.json({ message: "Payment successful!", tx: result.paymentReceipt });} else {// send payment statusreturn new NextResponse(JSON.stringify(result.responseBody),{status: result.status,headers: { "Content-Type": "application/json", ...(result.responseHeaders || {}) },});}} catch(error) {console.error(error);return new NextResponse(JSON.stringify({ error: "server error" }),{status: 500,headers: { "Content-Type": "application/json" },});}}
Step 5: Client-side setup (consuming paid endpoint)
Below is an example of consuming the paid endpoint using a Next.js app, however the endpoint can be consumed via an agent script as well.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667"use client";import { useState } from "react";import { createThirdwebClient } from "thirdweb";import { wrapFetchWithPayment } from "thirdweb/x402";import { createWallet } from "thirdweb/wallets";const client = createThirdwebClient({clientId: process.env.NEXT_PUBLIC_CLIENT_ID || "YOUR_PUBLIC_CLIENT_ID",});export default function Home() {const [message, setMessage] = useState("Pay $0.0001 USDC to unlock premium content");const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");const payAndFetch = async () => {setStatus("loading");setMessage("Connecting wallet...");try {const wallet = createWallet("io.metamask");await wallet.connect({ client });setMessage("Wallet connected — paying...");const fetchPay = wrapFetchWithPayment(fetch, client, wallet);const res = await fetchPay("/api/premium");const json = await res.json();setStatus("success");setMessage(json.message || "Payment successful!");} catch (e: any) {setStatus("error");setMessage(e.message);}};return (<main className="min-h-screen bg-zinc-950 flex items-center justify-center p-6"><div className="max-w-md w-full space-y-6"><div className="text-center space-y-2"><h1 className="text-2xl font-bold text-white">x402 on Monad</h1><p className="text-zinc-400 text-sm">Micropayments via Thirdweb facilitator.{" "}<a href="https://docs.monad.xyz/guides/x402-guide" className="text-violet-400 hover:underline">Docs</a></p></div><buttononClick={payAndFetch}disabled={status === "loading"}className="w-full py-3 px-4 bg-violet-600 hover:bg-violet-500 disabled:bg-violet-800 disabled:cursor-wait text-white font-medium rounded-lg transition-colors">{status === "loading" ? "Processing..." : "Pay & Unlock Content"}</button><div className={`p-4 rounded-lg text-sm ${status === "error" ? "bg-red-950 text-red-300" :status === "success" ? "bg-green-950 text-green-300" :"bg-zinc-900 text-zinc-300"}`}>{message}</div></div></main>);}
Running Your x402 App
Now you're ready to test your x402 payment flow:
-
Start your development server:
npm run dev -
Open http://localhost:3000 in your browser
-
Click "Pay & Unlock Content"
-
Connect your wallet
-
Approve the USDC payment
-
See the content unlock instantly!
What's Next?
You've successfully built an x402 payment-enabled app on Monad! Here are some ideas to extend your implementation:
- Add more payable endpoints - Create different pricing tiers for various content or API calls
- Build AI agent integrations - Enable autonomous agents to pay for and access your APIs
Resources
Need Help?
If you run into issues or have questions, join the Monad Developer Discord
Happy building!