Skip to main content

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:

  1. Client requests a resource
  2. Server responds 402 with a small JSON payment requirement
  3. Client pays with a signed transaction
  4. 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, low cost, and avoid mempool congestion, perfect when many AI agents pay per API call.

Core Flow (Direct Payment)

A facilitator service is optional but recommended in production. Facilitators can batch transactions, cover gas, handle refunds, and simplify client logic.

Building 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:

  1. Visit https://faucet.circle.com
  2. Select USDC as the token
  3. Select Monad Testnet from the Network dropdown
  4. Enter your wallet address
  5. Click Send 1 USDC

Limit: One request per (stablecoin, testnet) pair per hour

circle_faucet

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-app

When prompted, select the following options:

  • ✅ TypeScript
  • ✅ ESLint
  • ✅ Tailwind CSS
  • src/ directory
  • ✅ App Router
  • ❌ Turbopack (optional)
  • Customize default import alias: @/* (default)

Navigate to your project:

cd my-x402-app

Step 2: Create a Thirdweb account and get your API keys

  1. Go to https://thirdweb.com/dashboard

  2. Sign in (wallet or email/Google)

  3. Create a project

  4. Go to Settings → API Keys

  5. You'll see two keys:

    • Client ID (for frontend)
    • Secret Key (for backend - keep this secure!)
  6. Add to your .env file:

    NEXT_PUBLIC_CLIENT_ID=your_client_id_here
    SECRET_KEY=your_secret_key_here

    thirdweb_keys

Step 3: Create a server wallet

A server wallet can be used to receive payments and interact with the blockchain from your backend.

  1. In your Thirdweb dashboard, navigate to Wallets > Server Wallets

  2. Click Wallets

  3. You should already see a server wallet by default or you can choose to create a new one

  4. Copy the wallet address and add it to your .env file as SERVER_WALLET

    NEXT_PUBLIC_CLIENT_ID=your_client_id_here
    SECRET_KEY=your_secret_key_here
    SERVER_WALLET=0xYourWallet

    server_wallet

Step 4: Create a server side payable endpoint

route.tssrc > app > api > premium
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
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",
method: "GET",
paymentData: req.headers.get("x-payment"),
network: monadTestnet, // payable on monad testnet
price: "$0.0001", // Amount per request
payTo: process.env.SERVER_WALLET!, // payment receiver
facilitator: twFacilitator,
});
if (result.status === 200) {
// If payment is settled, return paid response
return NextResponse.json({ message: "Paid! Monad is blazing fast ⚡", tx: result.paymentReceipt });
} else {
// send payment status
return 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.

page.tsxsrc > app
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
"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("Click to pay $0.0001 USDC (zero gas on Monad testnet)");
const payAndFetch = async () => {
setMessage("Connecting wallet...");
try {
const wallet = createWallet("io.metamask");
await wallet.connect({ client });
setMessage("Wallet connected — paying...");
const fetchPay = wrapFetchWithPayment(fetch, client, wallet);
// payable endpoint
const res = await fetchPay("/api/premium"); // relative URL = no CORS
const json = await res.json();
setMessage("PAID SUCCESSFULLY! 🎉\n\n" + JSON.stringify(json, null, 2));
} catch (e: any) {
setMessage("ERROR: " + e.message);
}
};
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<div style={{ padding: 24, fontFamily: "system-ui, sans-serif" }}>
<h1>Monad testnet x402 — Thirdweb</h1>
{/* Button to pay and fetch the premium content */}
<button onClick={payAndFetch} style={{ padding: 12, fontSize: 16 }}>
Pay & Unlock Content
</button>
{/* Once paid, the content will be fetched and displayed here: */}
<pre style={{ marginTop: 16, background: "#111", color: "#0f0", padding: 12 }}>
{message}
</pre>
</div>
</main>
</div>
)
};

Running Your x402 App

Now you're ready to test your x402 payment flow:

  1. Start your development server:

    npm run dev
  2. Open http://localhost:3000 in your browser

  3. Click "Pay & Unlock Content"

  4. Connect your wallet

  5. Approve the USDC payment

  6. 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!