Quickstart for Sellers
Accept payments in any token for your APIs and services using AnySpend x402 middleware
Overview
This guide shows you how to add AnySpend x402 payment support to your Express API, enabling you to accept payments in any token while receiving the token of your choice. With just a few lines of code, your API can monetize endpoints and handle automatic payment verification and settlement.
Key Benefits:
- Buyers can pay with any token they hold (ETH, DAI, USDC, etc.)
- Sellers can receive any token they prefer (USDC, B3, DAI, custom tokens, etc.)
- AnySpend automatically handles all token conversions
- No blockchain infrastructure or wallet management required
Prerequisites
Before you begin, make sure you have:
- Node.js 18+ installed
- An Express.js API (or willingness to create one)
- A wallet address to receive USDC payments
- Basic knowledge of Express middleware
Installation
Install the AnySpend x402 package:
npm install @b3dotfun/anyspend-x402yarn add @b3dotfun/anyspend-x402pnpm add @b3dotfun/anyspend-x402Quick start
1. Basic integration
The simplest way to add payments to your Express API:
typescriptimport express from 'express'; import { paymentMiddleware, facilitator } from '@b3dotfun/anyspend-x402'; const app = express(); // Define your payment routes and prices const endpointConfig = { '/api/premium-data': { amount: '1.00', // $1.00 in USDC asset: 'USD' // Will be converted to USDC }, '/api/ai-inference': { amount: '0.50', // $0.50 in USDC asset: 'USD' } }; // Your USDC receiving address const recipientAddress = '0xYourWalletAddress...'; // Apply payment middleware app.use(paymentMiddleware( recipientAddress, endpointConfig, facilitator // Pre-configured Anyspend facilitator )); // Define your protected routes app.get('/api/premium-data', (req, res) => { // This only runs after successful payment res.json({ data: 'Your premium data here', message: 'Payment successful!' }); }); app.get('/api/ai-inference', (req, res) => { res.json({ result: 'AI inference result', model: 'gpt-4' }); }); app.listen(3000, () => { console.log('API with x402 payments running on port 3000'); });
What this does:
- Clients request your API endpoint
- Middleware returns
402 Payment Requiredwith payment details - Client pays with their preferred token (ETH, DAI, etc.)
- AnySpend facilitator converts to USDC and settles to your wallet
- Your route handler executes and returns data
2. Receive custom tokens (e.g., B3)
You can configure your API to receive any token, not just USDC:
typescriptimport express from 'express'; import { paymentMiddleware, facilitator } from '@b3dotfun/anyspend-x402'; import { Address } from 'viem'; const app = express(); const PAYTO_ADDRESS = '0xYourWalletAddress...' as Address; const NETWORK = 'base' as const; // Configure to receive B3 tokens const endpointConfig = { 'POST /api/premium': { price: { amount: '100000000000000000000', // 100 B3 tokens (18 decimals) asset: { address: '0xB3B32F9f8827D4634fE7d973Fa1034Ec9fdDB3B3' as Address, decimals: 18, eip712: { name: 'B3', version: '1', }, }, }, network: NETWORK, config: { description: 'Premium data access', mimeType: 'application/json', }, } }; app.use(paymentMiddleware(PAYTO_ADDRESS, endpointConfig, facilitator)); app.post('/api/premium', (req, res) => { // Payment verified - you received 100 B3 tokens! res.json({ data: 'Premium ETH price history', paymentReceived: '100 B3 tokens' }); }); app.listen(3000);
What's different:
- Buyers can still pay with any token (ETH, USDC, DAI, etc.)
- AnySpend converts their payment to B3 tokens
- You receive exactly 100 B3 tokens in your wallet
- Both buyers and sellers keep their token preferences
Configuration
Facilitator URL
The AnySpend facilitator is hosted at:
texthttps://mainnet.anyspend.com/x402
The @b3dotfun/anyspend-x402 package includes a pre-configured facilitator instance:
typescriptimport { facilitator } from '@b3dotfun/anyspend-x402'; // Pre-configured to use mainnet.anyspend.com/x402 app.use(paymentMiddleware(recipientAddress, endpointConfig, facilitator));
Endpoint configuration
You have three methods to configure payment requirements:
Method 1: simple dollar amounts (recommended)
Let AnySpend handle token conversion automatically:
typescriptconst endpointConfig = { '/api/data': { amount: '1.00', // $1.00 worth of any token asset: 'USD' }, '/api/compute': { amount: '2.50', asset: 'USD' } };
Benefits:
- Clients can pay with any supported token
- AnySpend converts to USDC automatically
- You always receive USDC
Method 2: specific token requirements
Require specific tokens on specific networks. You can receive any token, not just USDC!
Example 1: receive USDC (stablecoin)
typescriptconst endpointConfig = { '/api/data': { amount: '1000000', // 1 USDC (6 decimals) asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base network: 'base-mainnet' } };
Example 2: receive B3 tokens (custom token)
typescriptimport { Address } from 'viem'; const PAYTO_ADDRESS = '0xYourWalletAddress...' as Address; const NETWORK = 'base' as const; const endpointConfig = { 'POST /api/premium': { price: { amount: '100000000000000000000', // 100 B3 tokens (100 * 10^18) asset: { address: '0xB3B32F9f8827D4634fE7d973Fa1034Ec9fdDB3B3' as Address, decimals: 18, eip712: { name: 'B3', version: '1', }, }, }, network: NETWORK, config: { description: 'Access to premium ETH price history data from CoinGecko', mimeType: 'application/json', }, } }; app.use(paymentMiddleware(PAYTO_ADDRESS, endpointConfig, facilitator));
Example 3: receive DAI (stablecoin)
typescriptconst endpointConfig = { '/api/ai-inference': { amount: '1000000000000000000', // 1 DAI (18 decimals) asset: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', // DAI on Base network: 'base-mainnet' } };
Example 4: receive native ETH
typescriptconst endpointConfig = { '/api/compute': { amount: '500000000000000', // 0.0005 ETH (18 decimals) asset: '0x0000000000000000000000000000000000000000', // Native ETH network: 'base-mainnet' } };
Example 5: receive custom ERC-20 token
For any ERC-20 token with permit support:
typescriptconst endpointConfig = { '/api/data': { price: { amount: '1000000000000000000', // 1 token (adjust decimals) asset: { address: '0xYourTokenAddress...' as Address, decimals: 18, // Your token's decimals eip712: { name: 'YourTokenName', // From token contract version: '1', // EIP-712 version }, }, }, network: 'base-mainnet', config: { description: 'Your API endpoint description', mimeType: 'application/json', }, } };
Key Points:
- Buyers can still pay with any token they hold
- AnySpend automatically converts their token to the one you want to receive
- You receive exactly the token you specified in your configuration
- Both buyer and seller flexibility are maintained
Finding token details
To configure a custom token, you need to know its EIP-712 parameters. Here's how to find them:
Method 1: check token contract
Use a blockchain explorer like Basescan:
typescript// Read from the token contract const name = await token.read.name(); // e.g., "B3" const version = await token.read.version(); // e.g., "1" const decimals = await token.read.decimals(); // e.g., 18
Method 2: common tokens
| Token | Name | Version | Decimals | Base Address |
|---|---|---|---|---|
| USDC | USD Coin | 2 | 6 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| DAI | Dai Stablecoin | 1 | 18 | 0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb |
| B3 | B3 | 1 | 18 | 0xB3B32F9f8827D4634fE7d973Fa1034Ec9fdDB3B3 |
Method 3: use viem
typescriptimport { createPublicClient, http } from 'viem'; import { base } from 'viem/chains'; const client = createPublicClient({ chain: base, transport: http() }); const [name, version, decimals] = await Promise.all([ client.readContract({ address: tokenAddress, abi: [{ name: 'name', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] }], functionName: 'name' }), client.readContract({ address: tokenAddress, abi: [{ name: 'version', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] }], functionName: 'version' }), client.readContract({ address: tokenAddress, abi: [{ name: 'decimals', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint8' }] }], functionName: 'decimals' }) ]);
Method 3: flexible (let client choose)
Allow clients to specify their preferred payment token:
typescriptconst endpointConfig = { '/api/data': { amount: '1.00', asset: 'USD', flexible: true // Accept X-PREFERRED-TOKEN header } };
Clients can then specify their token preference:
bashcurl https://api.example.com/data \ -H "X-PREFERRED-TOKEN: 0x4200000000000000000000000000000000000006" \ -H "X-PREFERRED-NETWORK: base-mainnet"
Advanced configuration
Dynamic pricing
Calculate prices based on request parameters:
typescriptimport { paymentMiddleware, facilitator } from '@b3dotfun/anyspend-x402'; app.use('/api/compute', (req, res, next) => { // Calculate price based on compute units requested const units = parseInt(req.query.units as string) || 1; const pricePerUnit = 0.10; // $0.10 per unit const totalPrice = (units * pricePerUnit).toFixed(2); // Attach dynamic config to request req.x402Config = { amount: totalPrice, asset: 'USD' }; next(); }, paymentMiddleware(recipientAddress, { '/api/compute': (req) => req.x402Config // Dynamic config }, facilitator)); app.get('/api/compute', (req, res) => { const units = parseInt(req.query.units as string) || 1; res.json({ result: `Computed ${units} units`, cost: req.x402Config.amount }); });
Custom facilitator configuration
If you need custom facilitator settings:
typescriptimport { Facilitator } from '@b3dotfun/anyspend-x402'; const customFacilitator = new Facilitator({ baseUrl: 'https://mainnet.anyspend.com/x402', apiKey: process.env.CDP_API_KEY_ID, // Optional: Coinbase CDP apiSecret: process.env.CDP_API_KEY_SECRET, timeout: 30000, // 30s timeout retries: 3 // Retry failed requests }); app.use(paymentMiddleware( recipientAddress, endpointConfig, customFacilitator ));
Multiple recipients
Route payments to different wallets based on endpoint:
typescriptconst endpointConfig = { '/api/service-a': { amount: '1.00', asset: 'USD', recipient: '0xWalletA...' }, '/api/service-b': { amount: '2.00', asset: 'USD', recipient: '0xWalletB...' } }; // Pass null as recipient, use per-endpoint recipients app.use(paymentMiddleware(null, endpointConfig, facilitator));
Protected routes
Protect specific routes
Only protect endpoints that require payment:
typescript// Public route - no payment required app.get('/api/public', (req, res) => { res.json({ message: 'This is free!' }); }); // Protected route - payment required app.use('/api/premium', paymentMiddleware( recipientAddress, { '/api/premium': { amount: '1.00', asset: 'USD' } }, facilitator )); app.get('/api/premium', (req, res) => { res.json({ message: 'This cost money!' }); });
Route-specific middleware
Apply different payment configs to different route groups:
typescript// Basic tier - $0.50 app.use('/api/basic', paymentMiddleware( recipientAddress, { '/api/basic/*': { amount: '0.50', asset: 'USD' } }, facilitator )); // Premium tier - $2.00 app.use('/api/premium', paymentMiddleware( recipientAddress, { '/api/premium/*': { amount: '2.00', asset: 'USD' } }, facilitator ));
Testing
Local development
Test your API locally with testnet USDC:
typescriptimport { Facilitator } from '@b3dotfun/anyspend-x402'; const testFacilitator = new Facilitator({ baseUrl: 'https://testnet.anyspend.com/x402', // Testnet facilitator }); const endpointConfig = { '/api/test': { amount: '0.01', asset: 'USD', network: 'base-sepolia' // Use testnet } }; app.use(paymentMiddleware( '0xYourTestWallet...', endpointConfig, testFacilitator ));
Get testnet USDC from the Coinbase Faucet.
Test client request
Test with curl:
bash# 1. Get payment requirements curl https://your-api.com/api/premium-data # Response: 402 Payment Required # { # "x402": "0.2", # "requirements": [{ # "scheme": "exact", # "network": "base-mainnet", # "amount": "1000000", # "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # "recipient": "0xYourWallet..." # }] # } # 2. Make payment with X-PAYMENT header (requires signed payload) curl https://your-api.com/api/premium-data \ -H "X-PAYMENT: eyJ4NDAyIjoi..." \ -H "Content-Type: application/json"
Or use the x402 client SDK (see Quickstart for Buyers).
Monitoring payments
Access payment metadata
The middleware attaches payment information to the request:
typescriptapp.get('/api/premium', (req, res) => { // Access payment details const payment = req.x402Payment; console.log('Payment received:', { txHash: payment.txHash, network: payment.network, amount: payment.amount, token: payment.token, from: payment.from }); res.json({ data: 'Your premium data', paymentTx: payment.txHash }); });
Webhook notifications
Get notified when payments are settled:
typescriptimport { paymentMiddleware, facilitator } from '@b3dotfun/anyspend-x402'; app.use(paymentMiddleware( recipientAddress, endpointConfig, facilitator, { onPaymentSettled: async (payment) => { console.log('Payment settled:', payment); // Log to database await db.payments.create({ txHash: payment.txHash, amount: payment.amount, from: payment.from, timestamp: new Date() }); // Send notification await sendEmail({ to: 'admin@example.com', subject: 'Payment Received', body: `Received ${payment.amount} USDC from ${payment.from}` }); } } ));
Examples
AI agent API
typescriptimport express from 'express'; import { paymentMiddleware, facilitator } from '@b3dotfun/anyspend-x402'; const app = express(); app.use(express.json()); const endpointConfig = { '/api/chat': { amount: '0.10', // $0.10 per request asset: 'USD' }, '/api/image': { amount: '0.50', // $0.50 per image asset: 'USD' } }; app.use(paymentMiddleware( process.env.USDC_WALLET_ADDRESS, endpointConfig, facilitator )); app.post('/api/chat', async (req, res) => { const { message } = req.body; // Call your AI service const response = await openai.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: message }] }); res.json({ response: response.choices[0].message.content, payment: req.x402Payment.txHash }); }); app.post('/api/image', async (req, res) => { const { prompt } = req.body; const image = await openai.images.generate({ model: 'dall-e-3', prompt }); res.json({ imageUrl: image.data[0].url, payment: req.x402Payment.txHash }); }); app.listen(3000);
Data API with rate limiting
typescriptconst endpointConfig = { '/api/data/basic': { amount: '0.01', // $0.01 per request asset: 'USD' }, '/api/data/premium': { amount: '0.10', // $0.10 per request asset: 'USD' } }; // Track usage per payer const usage = new Map(); app.use(paymentMiddleware( recipientAddress, endpointConfig, facilitator, { onPaymentSettled: (payment) => { const count = usage.get(payment.from) || 0; usage.set(payment.from, count + 1); } } )); app.get('/api/data/premium', (req, res) => { const payer = req.x402Payment.from; const requests = usage.get(payer) || 0; res.json({ data: getPremiumData(), requestCount: requests, discount: requests > 100 ? '10%' : 'none' }); });
Best practices
Store sensitive data in environment variables:
typescriptconst recipientAddress = process.env.USDC_WALLET_ADDRESS; const apiKey = process.env.CDP_API_KEY_ID;
Log all payment events for debugging and analytics:
typescript{ onPaymentSettled: (payment) => { logger.info('Payment received', { txHash: payment.txHash, amount: payment.amount, from: payment.from, endpoint: payment.endpoint }); } }
Provide clear error messages when payments fail:
typescriptapp.use((err, req, res, next) => { if (err.code === 'X402_PAYMENT_FAILED') { res.status(402).json({ error: 'Payment verification failed', message: 'Please try again with a valid payment' }); } next(err); });
Always test with testnet before deploying to production:
typescriptconst facilitatorUrl = process.env.NODE_ENV === 'production' ? 'https://mainnet.anyspend.com/x402' : 'https://testnet.anyspend.com/x402';
What's next
Troubleshooting
Check that:
- Your facilitator URL is correct:
https://mainnet.anyspend.com/x402 - Your recipient address is valid
- The network configuration matches the client's network
Make sure your endpoint config allows flexible payments:
typescript{ '/api/endpoint': { amount: '1.00', asset: 'USD', flexible: true // Allow any token } }
Verify:
- Your recipient address is correct
- You're checking the correct network
- Payment was settled (check
req.x402Payment.txHash)
Getting help
- Discord: Join our Discord community
- GitHub: github.com/b3-fun/anyspend-x402
- Documentation: x402.org/docs
- Examples: See full examples in the repository