Implementing Coinbase's x402 Protocol for Pay-per-use APIs in Next.js
Complete guide to implementing HTTP 402 Payment Required with Coinbase's x402 protocol. Build micropayment-gated APIs in your Next.js application with crypto payments.
Implementing Coinbase's x402 Protocol for Pay-per-use APIs in Next.js
The x402 protocol brings HTTP 402 (Payment Required) to life for the first time. Instead of returning errors, your API can now request payment and receive it in milliseconds using crypto micropayments.
What is x402?
HTTP 402 has been "reserved for future use" since 1997. The x402 protocol, pioneered by Coinbase, finally implements it:
- Client requests a resource
- Server returns
402 Payment Requiredwith payment details - Client sends payment (USDC on Base)
- Server returns the resource
This enables true pay-per-use APIs without subscriptions, API keys, or credit cards.
Why x402 Matters for Web3
Traditional API Monetization
- Requires user accounts
- Credit card processing fees (2.9% + $0.30)
- Monthly subscriptions for occasional users
- Chargebacks and fraud
x402 Micropayments
- No accounts needed
- Near-zero fees on Base L2
- Pay exactly for what you use
- Instant, irreversible crypto payments
Prerequisites
- Next.js 14+ with App Router
- A Coinbase Developer Platform account
- USDC on Base for testing
Step 1: Install Dependencies
pnpm add x402-next @coinbase/coinbase-sdk viem
The x402-next package provides Next.js middleware and utilities for implementing the protocol.
Step 2: Configure Environment
# .env.local
COINBASE_API_KEY=your_api_key
COINBASE_API_SECRET=your_api_secret
X402_RECEIVER_ADDRESS=0x... # Your wallet to receive payments
X402_NETWORK=base # or base-sepolia for testing
Step 3: Create Payment-Gated API Route
Here's a complete example of a premium API endpoint:
// app/api/premium/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withX402 } from 'x402-next';
// Define the handler for successful payments
async function handler(request: NextRequest) {
// This only runs after payment is verified
const data = await fetchPremiumData();
return NextResponse.json({
success: true,
data,
timestamp: new Date().toISOString(),
});
}
// Wrap with x402 payment requirement
export const GET = withX402(handler, {
// Price in USD (will be converted to USDC)
price: 0.001, // $0.001 per request
// Payment network
network: 'base',
// Your receiving address
receiver: process.env.X402_RECEIVER_ADDRESS!,
// Optional: Custom payment description
description: 'Premium API access - real-time data',
});
Step 4: Client-Side Implementation
Create a hook to handle x402 payments:
// hooks/useX402.ts
'use client';
import { useState, useCallback } from 'react';
import { useAccount, useSignMessage } from 'wagmi';
interface X402Response {
status: number;
paymentRequired?: {
amount: string;
currency: string;
receiver: string;
network: string;
};
data?: unknown;
}
export function useX402() {
const { address } = useAccount();
const { signMessageAsync } = useSignMessage();
const [isPaying, setIsPaying] = useState(false);
const fetchWithPayment = useCallback(
async (url: string, options?: RequestInit): Promise<X402Response> => {
// First request - might return 402
const response = await fetch(url, options);
if (response.status !== 402) {
return {
status: response.status,
data: await response.json(),
};
}
// Payment required
const paymentDetails = await response.json();
setIsPaying(true);
try {
// Create payment authorization
const paymentAuth = await createPaymentAuth(
paymentDetails,
address!,
signMessageAsync
);
// Retry with payment header
const paidResponse = await fetch(url, {
...options,
headers: {
...options?.headers,
'X-Payment': paymentAuth,
},
});
return {
status: paidResponse.status,
data: await paidResponse.json(),
};
} finally {
setIsPaying(false);
}
},
[address, signMessageAsync]
);
return { fetchWithPayment, isPaying };
}
async function createPaymentAuth(
details: { amount: string; receiver: string },
sender: string,
signMessage: (args: { message: string }) => Promise<string>
): Promise<string> {
const message = `Pay ${details.amount} USDC to ${details.receiver}`;
const signature = await signMessage({ message });
return btoa(
JSON.stringify({
sender,
...details,
signature,
})
);
}
Step 5: Build a Payment-Aware Component
// components/PremiumDataFetcher.tsx
'use client';
import { useState } from 'react';
import { useX402 } from '@/hooks/useX402';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
export function PremiumDataFetcher() {
const { fetchWithPayment, isPaying } = useX402();
const [data, setData] = useState<unknown>(null);
const [cost, setCost] = useState(0);
const handleFetch = async () => {
const response = await fetchWithPayment('/api/premium/data');
if (response.data) {
setData(response.data);
setCost((prev) => prev + 0.001);
}
};
return (
<Card>
<CardHeader>
<CardTitle>Premium API Data</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<Button onClick={handleFetch} disabled={isPaying}>
{isPaying ? 'Processing Payment...' : 'Fetch Data ($0.001)'}
</Button>
<p className="text-sm text-muted-foreground">
Total spent: ${cost.toFixed(4)}
</p>
{data && (
<pre className="bg-muted p-4 rounded-lg text-sm overflow-auto">
{JSON.stringify(data, null, 2)}
</pre>
)}
</div>
</CardContent>
</Card>
);
}
Step 6: Integrate with GasX
Here's where it gets interesting. You can combine x402 with gas sponsorship:
// app/api/premium/sponsored/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withX402 } from 'x402-next';
import { sponsorTransaction } from '@/lib/gasx';
async function handler(request: NextRequest) {
const { txData, userAddress } = await request.json();
// After payment, sponsor the user's transaction
const sponsoredTx = await sponsorTransaction({
userAddress,
...txData,
});
return NextResponse.json({
success: true,
sponsoredTransaction: sponsoredTx,
});
}
export const POST = withX402(handler, {
price: 0.01, // $0.01 to sponsor a transaction
network: 'base',
receiver: process.env.X402_RECEIVER_ADDRESS!,
description: 'Gas sponsorship service',
});
This creates a pay-per-use gas sponsorship API!
Advanced: Tiered Pricing
Implement different prices based on the request:
// app/api/ai/generate/route.ts
import { withX402Dynamic } from 'x402-next';
async function handler(request: NextRequest) {
const { prompt, model } = await request.json();
const result = await generateWithAI(prompt, model);
return NextResponse.json({ result });
}
// Dynamic pricing based on model
export const POST = withX402Dynamic(handler, (request) => {
const body = request.body;
const model = body?.model || 'basic';
const pricing = {
basic: 0.001,
advanced: 0.005,
premium: 0.02,
};
return {
price: pricing[model] || pricing.basic,
network: 'base',
receiver: process.env.X402_RECEIVER_ADDRESS!,
};
});
Monitoring x402 Payments
Track your micropayment revenue:
// lib/x402-analytics.ts
import { prisma } from '@/lib/db';
export async function trackPayment(
endpoint: string,
amount: number,
sender: string
) {
await prisma.x402Payment.create({
data: {
endpoint,
amount,
sender,
timestamp: new Date(),
},
});
}
// Get revenue stats
export async function getRevenueStats(period: 'day' | 'week' | 'month') {
const since = new Date();
if (period === 'day') since.setDate(since.getDate() - 1);
if (period === 'week') since.setDate(since.getDate() - 7);
if (period === 'month') since.setMonth(since.getMonth() - 1);
const payments = await prisma.x402Payment.aggregate({
where: { timestamp: { gte: since } },
_sum: { amount: true },
_count: true,
});
return {
totalRevenue: payments._sum.amount || 0,
totalRequests: payments._count,
period,
};
}
Security Considerations
1. Verify Payments On-Chain
Always verify payment signatures and check on-chain if amounts are significant:
import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';
const client = createPublicClient({
chain: base,
transport: http(),
});
async function verifyPayment(txHash: string, expectedAmount: bigint) {
const receipt = await client.getTransactionReceipt({ hash: txHash });
// Verify transfer events match expected amount
}
2. Rate Limiting
Even with payments, implement rate limiting to prevent abuse:
import { Ratelimit } from '@upstash/ratelimit';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(100, '1 h'), // 100 paid requests/hour
});
3. Minimum Payment Thresholds
Set minimum payments to cover Base transaction fees:
const MIN_PAYMENT = 0.0001; // $0.0001 minimum
Use Cases for x402
- AI API Access: Pay per token/request
- Premium Data Feeds: Real-time market data
- Gas Sponsorship as a Service: What GasX does!
- Content Unlocking: Pay to read articles
- Compute Resources: Pay for processing time
GasX + x402 Integration
GasX uses x402 internally for our premium API tier. This allows:
- Pay-per-sponsored-transaction pricing
- No monthly commitments
- Instant access without signup
- Transparent, on-chain payments
Try it yourself: Our /api/sponsor endpoint accepts x402 payments for instant gas sponsorship.
Conclusion
The x402 protocol opens up entirely new business models for Web3 applications. Combined with gas sponsorship from GasX, you can build truly frictionless experiences where users pay micro-amounts for exactly what they use.
Resources
Ready to eliminate gas friction?
Create your first gas sponsorship campaign in under 5 minutes. No coding required.
Create Campaign