x402 Payment Protocol
The x402 protocol enables micropayments for accessing paid content. This guide explains how payments work in Grapevine.
What is x402?
x402 is a payment protocol that allows:
- Micropayments - Pay only for what you access
- 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:
- Accessing paid entries - Content marked with
is_free: false
- Creating feeds
- Creating entries
- Reading free entries
- All GET/list operations
Creating Paid Content
To monetize your content, create entries with is_free: false and a price:
import { GrapevineClient } from '@pinata/grapevine-sdk';
const grapevine = new GrapevineClient({
network: 'mainnet',
privateKey: process.env.PRIVATE_KEY
});
// Create a paid entry
const paidEntry = await grapevine.entries.create(feedId, {
title: 'Premium Market Report',
content: 'Exclusive analysis and insights...',
description: 'In-depth quarterly market analysis',
is_free: false,
price: {
amount: '1000000', // 1.00 USDC (6 decimals)
currency: 'USDC'
}
});
console.log('Paid entry created:', paidEntry.id);'1000000'= 1.00 USDC'500000'= 0.50 USDC'100000'= 0.10 USDC'10000'= 0.01 USDC
Accessing Paid Content
When a user tries to access paid content, the API returns a 402 Payment Required response. The SDK handles this automatically when using the appropriate methods.
Payment Flow
- Request paid content → API returns 402 with payment requirements
- Parse requirements → Extract payment details (amount, asset, network)
- Create payment → Sign transaction with x402 protocol
- Retry with payment → Include
X-PAYMENTheader - Access granted → Content returned successfully
Manual Payment Handling (Advanced)
For direct API integration without the SDK:
import { createPaymentHeader, selectPaymentRequirements } from 'x402/client';
import { PaymentRequirementsSchema } from 'x402/types';
import { facilitator } from '@coinbase/x402';
// 1. Request returns 402
const response = await fetch(`${API_URL}/v1/feeds/${feedId}/entries/${entryId}/access-link`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders
}
});
if (response.status === 402) {
const paymentData = await response.json();
// 2. Parse requirements
const requirements = paymentData.accepts.map(x =>
PaymentRequirementsSchema.parse(x)
);
// 3. Select matching network
const selected = selectPaymentRequirements(
requirements,
['base'], // or ['base-sepolia'] for testnet
'exact'
);
// 4. Create payment header
const paymentHeader = await createPaymentHeader(
walletClient,
parseInt(paymentData.x402Version),
selected,
facilitator
);
// 5. Retry with payment
const paidResponse = await fetch(`${API_URL}/v1/feeds/${feedId}/entries/${entryId}/access-link`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders,
'X-PAYMENT': paymentHeader
}
});
// Check for payment confirmation
if (paidResponse.headers.get('X-PAYMENT-RESPONSE')) {
console.log('Payment processed successfully');
}
const accessLink = await paidResponse.json();
console.log('Access URL:', accessLink.url);
}Payment Requirements Response
When a 402 is returned, the response includes payment requirements:
{
"error": "payment_required",
"x402Version": "0",
"accepts": [{
"scheme": "exact",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"network": "base",
"maxAmountRequired": "1000000"
}]
}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);
}Next Steps
- Creating Paid Entries - Learn how to create paid content
- Access Links - Generate access links for paid content
- Error Handling - Handle payment errors gracefully