Skip to main content

What is x402?

x402 is an HTTP payment protocol that enables gasless, signature-based payments to authenticate API requests. Instead of on-chain transactions, users sign an EIP-712 message that authorizes a payment facilitator to transfer tokens on their behalf. Key benefits:
  • ✅ No gas fees for users
  • ✅ Instant payment verification
  • ✅ Works over standard HTTP
  • ✅ Perfect for API monetization

Why Combine Them?

Many facilitators only accept specific tokens on specific chains. In addition, x402 requires EIP-3009 on the token contract further limiting token support. However, Trails enables x402 using any token from any chain. Trails + x402 creates a powerful payment flow:
  1. User pays with Trails using any token from any chain
  2. Payment arrives on your chosen chain and token that the facilitator supports (e.g., Base USDC)
  3. Automatic trigger of x402 signature for API access
  4. Gasless authentication to your protected endpoints
  5. API responds with paid content/service

Architecture

┌──────────────┐
│   Frontend   │
│   (React)    │
└──────┬───────┘

       │ 1. User clicks "Pay & Get Access"


┌──────────────────────┐
│   Trails Widget      │
│   (Cross-chain Pay)  │
└──────┬───────────────┘

       │ 2. Routes payment from any chain/token
       │    to Base USDC (or your target)


┌──────────────────────┐
│  onCheckoutComplete  │
│     (Callback)       │
└──────┬───────────────┘

       │ 3. Triggers x402 signature


┌──────────────────────┐
│  x402-axios          │
│  (Payment Signer)    │
└──────┬───────────────┘

       │ 4. Adds payment header to HTTP request


┌──────────────────────┐
│  Backend API         │
│  (x402-hono)         │
└──────┬───────────────┘

       │ 5. Verifies payment & transfers tokens


┌──────────────────────┐
│  Protected Response  │
│  (API Data)          │
└──────────────────────┘

Implementation

Frontend Setup

1. Install Dependencies

pnpm add 0xtrails wagmi viem x402-axios axios

2. Configure Providers

import { WagmiProvider, createConfig, http } from "wagmi"
import { RainbowKitProvider } from "@rainbow-me/rainbowkit"
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"
import { mainnet, base } from "wagmi/chains"

const config = createConfig({
  chains: [mainnet, base],
  transports: {
    [mainnet.id]: http(),
    [base.id]: http(),
    [baseSepolia.id]: http(),
  },
})

const queryClient = new QueryClient()

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <RainbowKitProvider>
          {children}
        </RainbowKitProvider>
      </QueryClientProvider>
    </WagmiProvider>
  )
}

3. Create x402 API Client

import axios from "axios"
import type { WalletClient } from "viem"
import { withPaymentInterceptor } from "x402-axios"

const baseApiClient = axios.create({
  baseURL: "http://localhost:3001",
  headers: { "Content-Type": "application/json" },
})

let apiClient = baseApiClient

// Update API client when wallet connects
export function updateApiClient(walletClient: WalletClient | null) {
  if (walletClient && walletClient.account) {
    // Attach x402 payment interceptor to axios
    apiClient = withPaymentInterceptor(baseApiClient, walletClient as any)
    console.log("💳 API client ready for payments")
  } else {
    apiClient = baseApiClient
  }
}

// API endpoints
export const api = {
  // Protected endpoint - requires x402 payment
  purchaseOneTimeAccess: async () => {
    const response = await apiClient.post("/api/pay/onetime")
    return response.data
  },
}
The withPaymentInterceptor automatically handles EIP-712 signature creation and attaches payment headers to HTTP requests.

4. Build Payment Widget Component

import { TrailsWidget } from "0xtrails/widget"
import type { TrailsWidgetRef } from "0xtrails/widget"
import { useState, useCallback, useRef } from "react"
import { useWalletClient, useSwitchChain } from "wagmi"
import { base } from "wagmi/chains"
import { api, updateApiClient } from "../services/api"

export default function PayWidget() {
  const widgetRef = useRef<TrailsWidgetRef | null>(null)
  const [loading, setLoading] = useState(false)
  const [result, setResult] = useState<string>("")
  const { data: walletClient } = useWalletClient()
  const { switchChainAsync } = useSwitchChain()

  // Handle Trails payment completion
  const handleTrailsComplete = useCallback(
    async (data: { sessionId: string }) => {
      console.log("✅ Trails payment complete:", data.sessionId)
      
      // Close Trails modal
      widgetRef.current?.closeModal?.()
      
      // Switch to payment network (Base)
      try {
        await switchChainAsync?.({ chainId: base.id })
      } catch (err) {
        console.error("Network switch failed:", err)
      }

      if (!walletClient) {
        setResult("❌ Wallet not connected")
        return
      }

      try {
        setLoading(true)
        setResult("🔄 Authorizing API access via x402...")
        
        // Update API client with wallet for x402 signing
        updateApiClient(walletClient)
        
        // Make payment-protected API request
        // x402-axios will automatically:
        // 1. Prompt user to sign EIP-712 message
        // 2. Add payment signature to request headers
        // 3. Facilitator verifies and transfers tokens
        const response = await api.purchaseOneTimeAccess()
        
        setResult(`✅ API access granted! Session: ${response.sessionId}`)
      } catch (error: any) {
        console.error("x402 payment error:", error)
        setResult(`❌ Payment failed: ${error.message}`)
      } finally {
        setLoading(false)
      }
    },
    [walletClient, switchChainAsync]
  )

  return (
    <div className="space-y-4">
      <TrailsWidget
        ref={widgetRef}
        mode="pay"
        theme="auto"
        toAddress="0xYourAddress"
        toAmount="0.1"
        toChainId={8453} // Base
        toToken="USDC"
        buttonText="Pay & Get Access"
        onCheckoutComplete={handleTrailsComplete}
        customCss={`
          --trails-border-radius-button: 9999px;
          --trails-primary: #4285F4;
          --trails-primary-hover: #357AE8;
        `}
      />
      
      {loading && <p>Processing...</p>}
      {result && <p>{result}</p>}
    </div>
  )
}
The onCheckoutComplete callback is the bridge between Trails and x402. It fires when the cross-chain payment succeeds, then triggers the x402 signature flow.

Backend Setup

1. Install Dependencies

pnpm add hono @hono/node-server x402-hono dotenv uuid

2. Configure Environment

# Your wallet address to receive x402 payments
ADDRESS=0xYourWalletAddress

# Network for x402 payments
NETWORK=base

# x402 Facilitator URL
FACILITATOR_URL=https://facilitator.payai.network

# Server Port
PORT=3001

3. Create x402 Protected API

import { Hono } from "hono"
import { serve } from "@hono/node-server"
import { cors } from "hono/cors"
import { paymentMiddleware, Network, Resource } from "x402-hono"
import { v4 as uuidv4 } from "uuid"
import { config } from "dotenv"

config()

const facilitatorUrl = process.env.FACILITATOR_URL as Resource
const payTo = process.env.ADDRESS as `0x${string}`
const network = process.env.NETWORK as Network || "base"
const port = parseInt(process.env.PORT || "3001")

const app = new Hono()

// Enable CORS for frontend
app.use("/*", cors({
  origin: ["http://localhost:5173"],
  credentials: true,
}))

// In-memory session storage (use Redis/DB in production)
const sessions = new Map<string, any>()

// Configure x402 payment middleware
app.use(
  paymentMiddleware(
    payTo, // Your wallet address
    {
      // Define paid endpoints and prices
      "/api/pay/onetime": {
        price: "$0.10",
        network,
      },
      "/api/pay/session": {
        price: "$1.00",
        network,
      },
    },
    {
      url: facilitatorUrl, // x402 facilitator
    }
  )
)

// Protected endpoint - requires x402 payment ($0.10)
app.post("/api/pay/onetime", async (c) => {
  // This code only runs if payment succeeds!
  const sessionId = uuidv4()
  const now = new Date()
  
  const session = {
    id: sessionId,
    createdAt: now,
    expiresAt: new Date(now.getTime() + 5 * 60 * 1000), // 5 min
    type: "onetime",
  }
  
  sessions.set(sessionId, session)
  
  // Return protected data/service
  return c.json({
    success: true,
    sessionId,
    message: "Payment verified! Access granted.",
    data: {
      // Your protected API response here
      exampleData: "This is only visible after payment",
    },
  })
})

// Free endpoint - health check
app.get("/api/health", (c) => {
  return c.json({
    status: "ok",
    network,
    payTo,
  })
})

serve({ fetch: app.fetch, port })
console.log(`🚀 x402 Server running on port ${port}`)
The paymentMiddleware intercepts requests to protected routes and verifies the x402 payment signature before allowing access.

Payment Flow Breakdown

Step 1: User Initiates Payment (Trails)

<TrailsWidget
  mode="pay"
  toAddress="0xYourAddress"
  toAmount="0.1"
  toChainId={8453} // Base
  toToken="USDC"
  onCheckoutComplete={handleTrailsComplete}
/>
The user clicks the Trails button and:
  • Selects their source chain and token (e.g., ETH on Arbitrum)
  • Trails calculates optimal route (swap → bridge → execute)
  • User approves transaction
  • Payment arrives as USDC on Base
Trails handles all complexity: token swaps, bridge routing, gas estimation, and delivery confirmation.

Step 2: Trails Callback Triggers x402

const handleTrailsComplete = useCallback(async (data) => {
  // Update API client with wallet for signing
  updateApiClient(walletClient)
  
  // Make x402-protected API request
  const response = await api.purchaseOneTimeAccess()
}, [walletClient])
When onCheckoutComplete fires:
  1. API client is configured with the user’s wallet
  2. HTTP request is made to protected endpoint
  3. x402 interceptor prompts for EIP-712 signature
  4. Signature is attached to request headers

Step 3: x402 Signature Creation

// x402-axios handles this automatically
withPaymentInterceptor(axiosInstance, walletClient)
The interceptor:
  1. Detects 402 Payment Required response
  2. Parses payment details from response headers
  3. Creates EIP-712 typed data structure
  4. Prompts user to sign (gasless, off-chain)
  5. Retries request with payment signature
Users sign a message, not a transaction. This is gasless and instant.

Step 4: Backend Verification

app.use(
  paymentMiddleware(
    payTo,
    {
      "/api/pay/onetime": { price: "$0.10", network: "base" },
    },
    { url: facilitatorUrl }
  )
)
The middleware:
  1. Validates payment signature
  2. Sends transfer request to facilitator
  3. Facilitator transfers tokens from user → your wallet
  4. If successful, request proceeds to handler
  5. If failed, returns 402 Payment Required

Step 5: Protected Response

app.post("/api/pay/onetime", (c) => {
  // Payment verified! Return protected data
  return c.json({
    success: true,
    data: "Your protected content",
  })
})
Only accessible if payment succeeds.

Conclusion

Combining Trails and x402 creates a seamless payment experience:
  • Trails handles the complexity of cross-chain payments
  • x402 provides gasless, instant API authentication
  • Together they enable truly user-friendly paid services - paid with any token from any chain.
This pattern works for:
  • 🔐 API monetization
  • 📰 Content paywalls
  • 🎮 Gaming microtransactions
  • 📊 Data services
  • 🤖 AI API access
  • 💼 SaaS applications
Start building payment-gated services today!
I