Skip to main content

X402 Protocol

The X402 protocol is a payment-aware HTTP extension that enables AI agents to require and verify payments for API access. This guide provides a complete overview of X402 implementation in the Nevermined Payments Library.

Overview of X402

X402 is an HTTP-based protocol for payment-protected resources:
  • 402 Payment Required: HTTP status code for payment requests
  • PAYMENT-SIGNATURE: Header containing payment credentials (X402 v2)
  • PAYMENT-REQUIRED: Header with payment requirements in 402 responses
  • PAYMENT-RESPONSE: Header with payment settlement details in success responses
  • Cryptographic Signatures: ERC-4337 account abstraction for secure payments

X402 Version 2 Specification

The Nevermined Payments Library implements X402 v2, which uses:
  • PAYMENT-SIGNATURE header for access tokens (replaces Authorization)
  • PAYMENT-REQUIRED header for payment requirements (replaces custom formats)
  • PAYMENT-RESPONSE header for settlement receipts
  • Structured payment credentials with cryptographic signatures

Generate X402 Access Tokens

Subscribers generate access tokens to query agents:
import { Payments, EnvironmentName } from '@nevermined-io/payments'

const subscriberPayments = Payments.getInstance({
  nvmApiKey: process.env.SUBSCRIBER_API_KEY!,
  environment: 'sandbox' as EnvironmentName,
})

// Generate X402 access token
const { accessToken } = await subscriberPayments.x402.getX402AccessToken(
  planId,              // Required: Plan ID
  agentId,             // Optional: Agent ID (restricts token to specific agent)
  redemptionLimit,     // Optional: Max credits this token can burn
  orderLimit,          // Optional: Order limit
  expiration           // Optional: Expiration date (ISO 8601 string)
)

console.log('X402 access token generated')

Generate Tokens via Nevermined App

Users can also generate X402 access tokens through the Nevermined App:
  1. Visit nevermined.app/permissions/agent-permissions
  2. Select the plan you’ve purchased
  3. Configure token parameters (agent, expiration, limits)
  4. Generate and copy the X402 access token
  5. Use the token in API requests
This provides a user-friendly interface for non-technical users to generate tokens without code.

X402 Access Token Structure

The access token is a JWT containing an X402 v2 payment credential:
{
  "x402Version": 2,
  "accepted": {
    "scheme": "nvm:erc4337",           // Payment scheme
    "network": "eip155:84532",         // Blockchain network (Base Sepolia)
    "planId": "plan-123",              // Payment plan ID
    "extra": {
      "version": "1",                  // Extra version info
      "agentId": "did:nv:agent-456"    // Optional agent restriction
    }
  },
  "payload": {
    "signature": "0x...",              // Cryptographic signature
    "authorization": {
      "from": "0xSubscriberAddress",   // Subscriber's wallet address
      "sessionKeysProvider": "zerodev", // Session key provider
      "sessionKeys": []                // Session keys for gasless transactions
    }
  },
  "extensions": {}                     // Optional extensions
}

Token Components

  • x402Version: Protocol version (2 for current spec)
  • accepted: Payment method specification
    • scheme: nvm:erc4337 (Nevermined ERC-4337 account abstraction)
    • network: Blockchain network (e.g., eip155:84532 for Base Sepolia)
    • planId: The payment plan being used
    • extra: Additional metadata (version, agentId, etc.)
  • payload: Payment authorization
    • signature: Cryptographic proof of payment authorization
    • authorization: Subscriber identity and session keys
  • extensions: Optional protocol extensions

Verify X402 Permissions

Agents verify tokens before executing requests:
import { Payments, EnvironmentName } from '@nevermined-io/payments'
import { buildPaymentRequired } from '@nevermined-io/payments'

const agentPayments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY!,  // Builder/agent API key
  environment: 'sandbox' as EnvironmentName,
})

// Build payment required specification
const paymentRequired = buildPaymentRequired(planId, {
  endpoint: '/api/v1/tasks',
  agentId: agentId,
  httpVerb: 'POST',
  network: 'eip155:84532',
  description: 'Task execution API',
})

// Verify subscriber permissions
const verification = await agentPayments.facilitator.verifyPermissions({
  paymentRequired,
  x402AccessToken: accessToken,
  maxAmount: 2n,  // Max credits to verify
})

if (verification.isValid) {
  console.log('✓ Permissions verified')
  console.log(`Subscriber: ${verification.agentRequest.subscriber}`)
} else {
  console.log('✗ Verification failed')
}

Verification Response

interface VerifyPermissionsResult {
  isValid: boolean           // True if token is valid
  agentRequest?: {
    agentRequestId: string   // Request ID for settlement
    subscriber: string       // Subscriber address
    balance: string          // Available credits
  }
}

Settle X402 Permissions

After successful execution, burn credits:
// Settle permissions (burn credits)
const settlement = await agentPayments.facilitator.settlePermissions({
  paymentRequired,
  x402AccessToken: accessToken,
  maxAmount: 2n,  // Credits to burn
  batch: false,   // Batch settlement (optional)
  marginPercent: 5,  // Add 5% margin (optional)
  agentRequestId: verification.agentRequest?.agentRequestId,  // From verification
})

console.log(`✓ Settlement successful`)
console.log(`Transaction: ${settlement.transaction}`)
console.log(`Credits burned: ${settlement.creditsRedeemed}`)
console.log(`Remaining balance: ${settlement.remainingBalance}`)

Settlement Response

interface SettlePermissionsResult {
  success: boolean          // True if settlement succeeded
  transaction: string       // Blockchain transaction hash
  creditsRedeemed: string   // Credits burned
  remainingBalance: string  // Subscriber's remaining credits
  network: string          // Blockchain network
}

HTTP Headers (X402 v2)

PAYMENT-SIGNATURE Header

Subscribers include this header in requests:
POST /api/v1/tasks HTTP/1.1
Host: agent.example.com
Content-Type: application/json
PAYMENT-SIGNATURE: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

{"prompt": "Hello"}

PAYMENT-REQUIRED Header (402 Response)

Agents return this header when payment is required:
HTTP/1.1 402 Payment Required
Content-Type: application/json
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwicmVzb3VyY2UiOnsidXJsIjoiL2FwaS92MS90YXNrcyJ9...

{"error": "Payment required"}
The header contains base64-encoded payment requirement JSON:
{
  "x402Version": 2,
  "resource": {
    "url": "/api/v1/tasks",
    "description": "Task execution API",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "nvm:erc4337",
      "network": "eip155:84532",
      "planId": "plan-123",
      "extra": {
        "version": "1",
        "agentId": "did:nv:agent-456"
      }
    }
  ],
  "extensions": {}
}

PAYMENT-RESPONSE Header (Success)

Agents include this header in successful responses:
HTTP/1.1 200 OK
Content-Type: application/json
PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4Li4uIn0=

{"result": "Task completed"}
The header contains base64-encoded settlement details:
{
  "success": true,
  "network": "eip155:84532",
  "transaction": "0x...",
  "creditsRedeemed": "2",
  "remainingBalance": "98"
}

Complete X402 Flow

Subscriber Side

import { Payments, EnvironmentName } from '@nevermined-io/payments'

const subscriberPayments = Payments.getInstance({
  nvmApiKey: process.env.SUBSCRIBER_API_KEY!,
  environment: 'sandbox' as EnvironmentName,
})

// 1. Generate access token
const { accessToken } = await subscriberPayments.x402.getX402AccessToken(
  planId,
  agentId
)

// 2. Make request with PAYMENT-SIGNATURE header
const response = await fetch('https://agent.example.com/api/v1/tasks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'PAYMENT-SIGNATURE': accessToken,
  },
  body: JSON.stringify({ prompt: 'Hello, agent!' }),
})

// 3. Handle response
if (response.status === 402) {
  // Payment required - need to purchase plan or top up
  const paymentRequired = response.headers.get('PAYMENT-REQUIRED')
  console.error('Payment required:', paymentRequired)
} else {
  const result = await response.json()
  const paymentResponse = response.headers.get('PAYMENT-RESPONSE')
  console.log('Success:', result)
  console.log('Payment:', paymentResponse)
}

Agent Side

import express from 'express'
import { Payments, EnvironmentName } from '@nevermined-io/payments'
import { buildPaymentRequired } from '@nevermined-io/payments'

const app = express()
app.use(express.json())

const agentPayments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY!,
  environment: 'sandbox' as EnvironmentName,
})

app.post('/api/v1/tasks', async (req, res) => {
  // 1. Extract token
  const accessToken = req.headers['payment-signature'] as string

  if (!accessToken) {
    const paymentRequired = buildPaymentRequired(planId, {
      endpoint: req.url,
      agentId: agentId,
      httpVerb: req.method,
    })

    return res
      .status(402)
      .set('PAYMENT-REQUIRED', Buffer.from(JSON.stringify(paymentRequired)).toString('base64'))
      .json({ error: 'Payment required' })
  }

  // 2. Build payment required
  const paymentRequired = buildPaymentRequired(planId, {
    endpoint: req.url,
    agentId: agentId,
    httpVerb: req.method,
  })

  try {
    // 3. Verify permissions
    const verification = await agentPayments.facilitator.verifyPermissions({
      paymentRequired,
      x402AccessToken: accessToken,
      maxAmount: 2n,
    })

    if (!verification.isValid) {
      return res
        .status(402)
        .set('PAYMENT-REQUIRED', Buffer.from(JSON.stringify(paymentRequired)).toString('base64'))
        .json({ error: 'Insufficient credits' })
    }

    // 4. Execute task
    const result = await processTask(req.body)

    // 5. Settle permissions
    const settlement = await agentPayments.facilitator.settlePermissions({
      paymentRequired,
      x402AccessToken: accessToken,
      maxAmount: 2n,
    })

    // 6. Return response with PAYMENT-RESPONSE header
    const paymentResponse = {
      success: settlement.success,
      network: settlement.network,
      transaction: settlement.transaction,
      creditsRedeemed: settlement.creditsRedeemed,
    }

    return res
      .status(200)
      .set('PAYMENT-RESPONSE', Buffer.from(JSON.stringify(paymentResponse)).toString('base64'))
      .json({ result })

  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})

app.listen(3000)

buildPaymentRequired Helper

Simplifies creating X402PaymentRequired objects:
import { buildPaymentRequired } from '@nevermined-io/payments'

const paymentRequired = buildPaymentRequired(
  planId,
  {
    endpoint: '/api/v1/tasks',      // Resource URL
    agentId: 'did:nv:agent-456',    // Agent ID
    httpVerb: 'POST',               // HTTP method
    network: 'eip155:84532',        // Blockchain (default)
    description: 'Task execution'   // Description
  }
)

Best Practices

  1. Always Verify Before Execute: Never skip token verification
  2. Settle After Success: Only burn credits after successful execution
  3. Use X402 v2 Headers: Prefer PAYMENT-SIGNATURE over Authorization
  4. Return 402 Properly: Include PAYMENT-REQUIRED header with details
  5. Log Transactions: Record settlement transaction hashes
  6. Handle Errors: Provide clear error messages in 402 responses
  7. Token Reuse: Subscribers can reuse tokens for multiple requests

Source References:
  • src/x402/token.ts (getX402AccessToken)
  • src/x402/facilitator-api.ts (verifyPermissions, settlePermissions, buildPaymentRequired)
  • tests/e2e/test_x402_e2e.test.ts (complete X402 flow)