x402 Payment Protocol
The x402 protocol enables micropayments for API operations. This guide explains how payments work in Grapevine.
What is x402?
x402 is a payment protocol that allows:
- Micropayments - Pay only for what you use
- On-chain settlements - Payments on Base blockchain
- USDC payments - Stablecoin for predictable costs
- Automatic processing - Seamless integration
When Payments Are Required
Payments are required for:
- Creating feeds
- Creating entries
- Some premium operations
Read operations (GET) are always free.
Payment Flow
1. Initial Request Returns 402
const response = await fetch(`${API_URL}/v1/feeds`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders
},
body: JSON.stringify({ name: 'My Feed' })
});
// Response status: 402 Payment Required2. Parse Payment Requirements
if (response.status === 402) {
const paymentData = await response.json();
// Payment data structure:
// {
// "x402Version": "0",
// "accepts": [
// {
// "scheme": "exact",
// "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
// "network": "base-sepolia",
// "maxAmountRequired": "1000000"
// }
// ]
// }
}3. Create Payment Header
import { createPaymentHeader, selectPaymentRequirements } from 'x402/client';
import { PaymentRequirementsSchema } from 'x402/types';
import { facilitator } from '@coinbase/x402';
// Parse and select requirements
const requirements = paymentData.accepts.map(x =>
PaymentRequirementsSchema.parse(x)
);
const selected = selectPaymentRequirements(
requirements,
['base-sepolia'], // or ['base'] for mainnet
'exact'
);
// Create payment header
const paymentHeader = await createPaymentHeader(
walletClient,
parseInt(paymentData.x402Version),
selected,
facilitator
);4. Retry with Payment
const paidResponse = await fetch(`${API_URL}/v1/feeds`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders,
'X-PAYMENT': paymentHeader
},
body: JSON.stringify({ name: 'My Feed' })
});
// Check for payment confirmation
if (paidResponse.headers.get('X-PAYMENT-RESPONSE')) {
console.log('Payment processed successfully');
}
const feed = await paidResponse.json();Complete Helper Function
async function makeRequestWithPayment(
url: string,
options: RequestInit
): Promise<Response> {
const response = await fetch(url, options);
if (response.status === 402) {
const paymentData = await response.json();
const x402Version = paymentData.x402Version;
const accepts = paymentData.accepts;
// Parse requirements
const requirements = accepts.map(x =>
PaymentRequirementsSchema.parse(x)
);
// Select matching network
const selected = selectPaymentRequirements(
requirements,
[network], // 'base' or 'base-sepolia'
'exact'
);
if (!selected) {
throw new Error(`No payment requirements match network: ${network}`);
}
// Create payment
const paymentHeader = await createPaymentHeader(
walletClient,
parseInt(x402Version),
selected,
facilitator
);
// Retry with payment
return fetch(url, {
...options,
headers: {
...options.headers,
'X-PAYMENT': paymentHeader
}
});
}
return response;
}Usage Example
// Use the helper for any request that might require payment
const feed = await makeRequestWithPayment(
`${API_URL}/v1/feeds`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders
},
body: JSON.stringify({
name: 'My Feed',
description: 'Test feed'
})
}
);Payment Costs
Feed Creation
- Testnet: Varies (test USDC)
- Mainnet: Varies (real USDC)
Entry Creation
- Testnet: Varies (test USDC)
- Mainnet: Varies (real USDC)
Costs are returned in the 402 response under maxAmountRequired.
Supported Networks
Base Sepolia (Testnet)
- Network:
base-sepolia - Chain ID: 84532
- Currency: Test USDC
- API:
https://api.grapevine.markets
Base (Mainnet)
- Network:
base - Chain ID: 8453
- Currency: USDC
- API:
https://api.grapevine.fyi
Getting USDC
Testnet (Base Sepolia)
- Get ETH from Alchemy Faucet
- Bridge USDC from Ethereum Sepolia
- Or contact Grapevine support for test tokens
Mainnet (Base)
- Buy USDC on an exchange
- Bridge to Base network
- Send to your wallet address
Error Handling
No Matching Network
const selected = selectPaymentRequirements(
requirements,
[network],
'exact'
);
if (!selected) {
throw new Error(
`Payment requires ${requirements[0].network} but wallet is on ${network}`
);
}Insufficient Balance
try {
const paymentHeader = await createPaymentHeader(...);
} catch (error) {
if (error.message.includes('insufficient')) {
console.error('Insufficient USDC balance');
// Prompt user to add funds
}
}Payment Failed
const response = await fetch(url, {
headers: { 'X-PAYMENT': paymentHeader }
});
if (!response.ok) {
const error = await response.json();
console.error('Payment failed:', error);
}Testing Payments
Use the test script to verify payment flow:
# Run on testnet
API_URL=https://api.grapevine.markets bun test-grapevine.tsThe script will:
- Create a feed (requires payment)
- Create entries (requires payment)
- Log payment details
- Verify successful transactions
Next Steps
- Error Handling Guide - Handle payment errors and 402 responses
- API Reference - Learn which operations require payment
- Error Handling - Handle payment errors