Implement x402

Add agentic payments to your stack with just a few lines of code.

Integration Steps

1. Define Pricing Logic

Determine which endpoints require payment and specify the price (e.g., 0.01 USDC). x402 supports dynamic pricing.

2. Add Middleware

Intercept requests on your server. If payment is missing, return `402 Payment Required` with the `WWW-Authenticate` header.

3. Handle Client Response

On the client (or agent), detect 402, parse the header, sign the payment, and retry the request with the proof.

Live Test Endpoint

You can test your agent or script against our live endpoint. It will always return 402 Payment Required with valid payment details for the Base network.

GET /api/x402CORS Enabled
curl -i https://402payment-test.com/api/x402
Returns headers: WWW-Authenticate: x402 address="..." amount="0.01" token="..."

Setting Up an Agent Wallet

To enable your agent to pay, it needs a crypto wallet. We recommend using the CDP AgentKit for the easiest integration, or you can manage keys manually.

Option A: CDP AgentKit (Recommended)

Best for AI Agents

The Coinbase Developer Platform (CDP) AgentKit provides a unified toolkit for creating AI agents that can interact with blockchain. Supports EVM chains (Base, Ethereum) and Solana.

  1. Install the SDK: npm install @coinbase/agentkit
  2. Configure your API Key and Private Key from CDP.
  3. Initialize the wallet provider.
import { CdpAgentkit } from "@coinbase/agentkit";

const agentKit = await CdpAgentkit.configureWithWallet({
  networkId: "base-sepolia",
  cdpApiKeyName: process.env.CDP_API_KEY_NAME,
  cdpApiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY,
});

const address = await agentKit.getWalletAddress();
console.log("Agent Address:", address);

Option B: Custom Wallet

For full control, you can manage a private key directly using standard libraries like viem or web3.py.

Node.js (viem)

import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount(process.env.PRIVATE_KEY);
console.log("Address:", account.address);

Python (web3.py)

from web3 import Web3

w3 = Web3(Web3.HTTPProvider(RPC_URL))
account = w3.eth.account.from_key(PRIVATE_KEY)
print("Address:", account.address)

Solana (@solana/web3.js)

import { Keypair } from '@solana/web3.js';

const keypair = Keypair.fromSecretKey(
  Buffer.from(process.env.SOLANA_PRIVATE_KEY, 'base64')
);
console.log("Address:", keypair.publicKey.toBase58());

Why Solana for x402?

Solana offers sub-second finality (~400ms) and ultra-low fees (~$0.00025), making it ideal for high-frequency micro-payments and AI agents.

Funding Your Agent

To pay for transactions, your agent needs ETH (for gas) and USDC (for payments) on Base Sepolia. You can get free testnet funds from the Coinbase Faucet.

Complete Code Examples

Ready-to-use implementations for Node.js and Python. These examples show the complete flow: detect 402, parse payment details, execute payment, and retry with proof.

Node.js (Complete)

viem + wagmi
import { createWalletClient, http, parseUnits } from 'viem';
import { privateKeyToAccount } => 'viem/accounts';
import { base } from 'viem/chains';

const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const ERC20_ABI = [{
  name: 'transfer',
  type: 'function',
  stateMutability: 'nonpayable',
  inputs: [
    { name: 'to', type: 'address' },
    { name: 'amount', type: 'uint256' }
  ],
  outputs: [{ name: '', type: 'bool' }]
}];

async function fetchWith402(url, privateKey) {
  // Initialize wallet
  const account = privateKeyToAccount(privateKey);
  const client = createWalletClient({
    account,
    chain: base,
    transport: http()
  });

  // 1. Initial request
  let res = await fetch(url);

  if (res.status === 402) {
    console.log('Payment Required');
    
    // 2. Parse payment details
    const auth = res.headers.get('WWW-Authenticate');
    const addressMatch = auth.match(/address="([^"]+)"/);
    const amountMatch = auth.match(/amount="([^"]+)"/);
    
    const recipient = addressMatch[1];
    const amount = amountMatch[1];
    
    console.log(`Paying ${amount} USDC to ${recipient}`);
    
    // 3. Execute payment
    const hash = await client.writeContract({
      address: USDC_ADDRESS,
      abi: ERC20_ABI,
      functionName: 'transfer',
      args: [recipient, parseUnits(amount, 6)]
    });
    
    console.log(`Payment sent: ${hash}`);
    
    // 4. Retry with proof
    res = await fetch(url, {
      headers: {
        'Authorization': `x402-proof ${hash}`
      }
    });
  }

  return res.json();
}

// Usage
const data = await fetchWith402(
  'https://402payment-test.com/api/x402',
  process.env.PRIVATE_KEY
);
console.log('Data:', data);

Python (Complete)

requests + web3.py
import requests
import re
from web3 import Web3
from eth_account import Account

# Base network RPC
w3 = Web3(Web3.HTTPProvider('https://mainnet.base.org'))

USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
ERC20_ABI = [{
    'name': 'transfer',
    'type': 'function',
    'stateMutability': 'nonpayable',
    'inputs': [
        {'name': 'to', 'type': 'address'},
        {'name': 'amount', 'type': 'uint256'}
    ],
    'outputs': [{'name': '', 'type': 'bool'}]
}]

def fetch_with_402(url, private_key):
    # Initialize account
    account = Account.from_key(private_key)
    
    # 1. Initial request
    res = requests.get(url)
    
    if res.status_code == 402:
        print('Payment Required')
        
        # 2. Parse payment details
        auth = res.headers['WWW-Authenticate']
        recipient = re.search(r'address="([^"]+)"', auth).group(1)
        amount = re.search(r'amount="([^"]+)"', auth).group(1)
        
        print(f'Paying {amount} USDC to {recipient}')
        
        # 3. Execute payment
        usdc = w3.eth.contract(
            address=USDC_ADDRESS,
            abi=ERC20_ABI
        )
        
        tx = usdc.functions.transfer(
            recipient,
            int(float(amount) * 10**6)  # USDC has 6 decimals
        ).build_transaction({
            'from': account.address,
            'nonce': w3.eth.get_transaction_count(account.address),
            'gas': 100000,
            'gasPrice': w3.eth.gas_price
        })
        
        signed = account.sign_transaction(tx)
        tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
        
        print(f'Payment sent: {tx_hash.hex()}')
        
        # 4. Retry with proof
        res = requests.get(url, headers={
            'Authorization': f'x402-proof {tx_hash.hex()}'
        })
    
    return res.json()

# Usage
import os
data = fetch_with_402(
    'https://402payment-test.com/api/x402',
    os.environ['PRIVATE_KEY']
)
print('Data:', data)

Implementation Flows

There are three main approaches to integrating x402: manual approval (human-in-the-loop), delegated authorization (approve once, agent pays), and autonomous agents (fully pre-authorized). The key difference is who controls the wallet and when.

Flow 1: Manual Approval (Human-in-the-Loop)

The user's wallet (e.g., MetaMask, Coinbase Wallet) prompts them to approve each payment. Best for user-facing applications where transparency is critical.

🔑 Authorization: Browser Wallet

  • • Private key stored in wallet extension (MetaMask/Coinbase Wallet)
  • • App uses wagmi to request signing
  • • Wallet shows popup asking for approval
  • • User clicks "Approve" in wallet UI
1

User initiates request

App calls API endpoint

2

402 response received

Parse payment details from header

3

Show payment prompt

UI displays amount and asks for approval

4

User approves in wallet

MetaMask/Coinbase Wallet popup

5

Payment executed & request retried

Transaction confirmed, resource served

// Example: Manual approval with wagmi
import { useAccount, useWriteContract } from 'wagmi';

function PayForResource() {
  const { address } = useAccount();
  const { writeContract } = useWriteContract();

  async function handleRequest() {
    const res = await fetch('/api/premium-data');
    
    if (res.status === 402) {
      const auth = res.headers.get('WWW-Authenticate');
      const { address: recipient, amount } = parseAuth(auth);
      
      // This triggers the wallet popup (MetaMask/Coinbase Wallet)
      // User must approve in their wallet - NO window.confirm needed!
      const tx = await writeContract({
        address: USDC_ADDRESS,
        abi: ERC20_ABI,
        functionName: 'transfer',
        args: [recipient, parseUnits(amount, 6)]
      });
      
      // Retry with proof
      const data = await fetch('/api/premium-data', {
        headers: { 'Authorization': `x402-proof ${tx}` }
      });
      return data.json();
    }
  }
}

Flow 2: Autonomous Agent (Pre-Authorized)

The agent has direct access to a wallet (via private key or delegated signer) and can pay automatically. Best for backend agents, bots, or trusted automation.

🔑 Authorization: Private Key

  • • Private key stored in environment variable
  • • Agent uses viem to sign directly
  • No popup - automatic signing
  • • No human intervention needed
1

Agent configured with wallet access

Private key in env vars or secure vault

2

Agent requests resource

Automated API call

3

402 detected, budget checked

Verify amount is within spending limits

4

Payment executed automatically

No human intervention

5

Request retried with proof

Resource served, transaction logged

// Example: Autonomous agent with budget control
import { privateKeyToAccount } from 'viem/accounts';
import { createWalletClient, http } from 'viem';
import { base } from 'viem/chains';

class AgentWallet {
  constructor(privateKey, dailyBudget) {
    this.account = privateKeyToAccount(privateKey);
    this.client = createWalletClient({
      account: this.account,
      chain: base,
      transport: http()
    });
    this.dailyBudget = dailyBudget;
    this.spentToday = 0;
  }

  async fetchWith402(url) {
    const res = await fetch(url);
    
    if (res.status === 402) {
      const { address, amount } = parseAuth(res.headers.get('WWW-Authenticate'));
      
      // Budget check
      if (this.spentToday + parseFloat(amount) > this.dailyBudget) {
        throw new Error('Daily budget exceeded');
      }
      
      // Automatic payment (no user prompt)
      const hash = await this.client.writeContract({
        address: USDC_ADDRESS,
        abi: ERC20_ABI,
        functionName: 'transfer',
        args: [address, parseUnits(amount, 6)]
      });
      
      this.spentToday += parseFloat(amount);
      console.log(`Auto-paid ${amount} USDC. Budget: ${this.spentToday}/${this.dailyBudget}`);
      
      // Retry with proof
      return fetch(url, {
        headers: { 'Authorization': `x402-proof ${hash}` }
      });
    }
    
    return res;
  }
}

// Usage
const agent = new AgentWallet(process.env.AGENT_PRIVATE_KEY, 10); // $10/day limit
const data = await agent.fetchWith402('/api/premium-data');

Flow 3: Delegated Authorization (Hybrid)

User approves once, granting the agent a time-limited or budget-limited permission to pay automatically. Best for user-controlled agents with spending limits.

🔑 Authorization: Delegated Permission

  • • User grants one-time approval (allowance/session key)
  • • Agent stores delegation token
  • No popup after initial approval
  • • User can revoke anytime
1

User grants permission once

Approve spending limit or create session key

2

Agent stores delegation token

Session key or allowance reference

3

Agent pays automatically (within limits)

No popup, but enforces budget/time constraints

4

User can revoke anytime

Permission is revocable on-chain

Option A: ERC-20 Allowance (Simplest)

User approves the agent contract to spend USDC on their behalf.

// User approves once (in browser)
await usdcContract.approve(agentContractAddress, parseUnits("100", 6));

// Agent can now spend up to 100 USDC without asking
// (runs on backend)
const agent = new AgentWithAllowance(userAddress);
await agent.payFor402(url, amount); // No popup!

// User can revoke
await usdcContract.approve(agentContractAddress, 0);

Option B: ERC-4337 Session Keys (Most Flexible)

User creates a temporary signing key with spending limits and expiration.

// User creates session key (in browser)
const sessionKey = await smartAccount.createSessionKey({
  validUntil: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
  spendingLimit: parseUnits("10", 6), // Max 10 USDC
  allowedTargets: [recipientAddress] // Only specific addresses
});

// Agent uses session key (backend)
const agent = new AgentWithSessionKey(sessionKey);
await agent.fetchWith402(url); // Pays automatically within limits

// Session key expires after 24h or can be revoked

Option C: Safe Wallet Modules (Enterprise)

Add agent as a Safe module with granular permissions.

// User enables module (in Safe UI)
await safe.enableModule(agentModuleAddress);

// Agent executes payments (backend)
await agentModule.executePayment({
  to: recipient,
  amount: parseUnits("0.01", 6),
  // Safe enforces rules: daily limits, whitelisted addresses, etc.
});

// User can disable module anytime
await safe.disableModule(agentModuleAddress);

✅ Benefits of Delegated Authorization

  • User retains control (can revoke anytime)
  • Agent can pay automatically (no popups)
  • Spending limits + time constraints
  • No need to share private keys
  • Auditable on-chain

How to Authorize an Agent to Pay Automatically

📝 How to Get a Private Key for Your Agent

For Flow 2 (Autonomous Agent), you need a private key. Here's how to create one:

Method 1: Generate with viem (Recommended)
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';

// Generate a new random private key
const privateKey = generatePrivateKey();
console.log("Private Key:", privateKey);

// Derive the address
const account = privateKeyToAccount(privateKey);
console.log("Address:", account.address);

// Save privateKey to .env file (NEVER commit to git!)
// AGENT_PRIVATE_KEY=0x...
Method 2: Use an Existing Wallet

You can export a private key from MetaMask or Coinbase Wallet:

  1. Open your wallet extension
  2. Go to Settings → Security & Privacy
  3. Click "Show Private Key" (requires password)
  4. Copy the key and store it securely

⚠️ Warning: Never use your personal wallet's private key for agents! Create a dedicated wallet for your agent with limited funds.

Method 3: CDP AgentKit (Easiest)

CDP AgentKit handles wallet creation automatically:

import { CdpAgentkit } from "@coinbase/agentkit";

const agentKit = await CdpAgentkit.configureWithWallet({
  networkId: "base-sepolia",
  cdpApiKeyName: process.env.CDP_API_KEY_NAME,
  cdpApiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY,
});

// AgentKit manages the wallet for you!
const address = await agentKit.getWalletAddress();

✅ Option A: Environment Variables (Simplest)

Store the agent's private key in environment variables. The agent signs transactions directly.

  • Pros: Simple, no extra infrastructure
  • Cons: If key leaks, unlimited spending
  • Best for: Trusted environments, low-value transactions

✅ Option B: Spending Limits + Allowlists (Recommended)

Agent has a wallet, but enforces budget limits and only pays approved addresses.

  • Pros: Limits blast radius if compromised
  • Cons: Requires custom logic
  • Best for: Production agents with moderate risk
const ALLOWED_RECIPIENTS = ['0x123...', '0x456...'];
if (!ALLOWED_RECIPIENTS.includes(recipient)) throw new Error('Unauthorized recipient');

✅ Option C: Multi-Sig or Approval Contracts (Most Secure)

Use a smart contract wallet (e.g., Safe) that requires multiple signatures or time-delayed approvals.

  • Pros: Maximum security, revocable permissions
  • Cons: Complex setup, higher gas costs
  • Best for: High-value agents, DAO-managed bots

⚠️ Security Best Practices

  • Never commit private keys to git
  • Use separate wallets for agents (don't reuse personal wallets)
  • Implement daily/hourly spending caps
  • Log all transactions for audit trails
  • Consider using hardware wallets or HSMs for high-value agents
Read Official Documentation