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

Supported Schemes

Nevermined supports two x402 payment schemes:
SchemeNetworkUse CaseSettlement
nvm:erc4337eip155:84532Crypto paymentsERC-4337 UserOps + session keys
nvm:card-delegationstripeFiat/credit cardStripe PaymentIntent + credit burn
The scheme is determined by the plan’s pricing configuration. Plans with isCrypto: false use nvm:card-delegation; all others use nvm:erc4337. The SDK auto-detects the scheme via resolveScheme().

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.

Generate Card-Delegation Tokens

For fiat plans using nvm:card-delegation, pass X402TokenOptions with a CardDelegationConfig:
import { X402TokenOptions, CardDelegationConfig } from '@nevermined-io/payments'

// USD card delegation
const tokenOptions: X402TokenOptions = {
  scheme: 'nvm:card-delegation',
  delegationConfig: {
    providerPaymentMethodId: 'pm_1AbCdEfGhIjKlM',
    spendingLimitCents: 10000,  // $100.00
    durationSecs: 2592000,       // 30 days
    currency: 'usd',
    maxTransactions: 100
  }
}

// EUR card delegation
const eurTokenOptions: X402TokenOptions = {
  scheme: 'nvm:card-delegation',
  delegationConfig: {
    providerPaymentMethodId: 'pm_1AbCdEfGhIjKlM',
    spendingLimitCents: 10000,  // €100.00 (in euro cents)
    durationSecs: 2592000,       // 30 days
    currency: 'eur',
    maxTransactions: 100
  }
}

// USD card delegation token
const { accessToken } = await subscriberPayments.x402.getX402AccessToken(
  planId,
  agentId,
  undefined,  // redemptionLimit
  undefined,  // orderLimit
  undefined,  // expiration
  tokenOptions
)

// EUR card delegation token
const { accessToken: eurAccessToken } = await subscriberPayments.x402.getX402AccessToken(
  planId,
  agentId,
  undefined,
  undefined,
  undefined,
  eurTokenOptions
)

Reusing Existing Delegations

Instead of creating a new delegation on every token request, you can reuse an existing delegation by passing its delegationId. This is useful when running multiple agents that should share a single spending budget:
const tokenOptions: X402TokenOptions = {
  scheme: 'nvm:card-delegation',
  delegationConfig: {
    delegationId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  }
}

const { accessToken } = await subscriberPayments.x402.getX402AccessToken(
  planId, agentId, undefined, undefined, undefined, tokenOptions
)
When delegationId is provided, the backend verifies that the delegation is active and that the requesting API key has access, then returns its existing token without creating a new delegation.

Specifying a Card

You can target a specific enrolled card using cardId. The backend will look for an active delegation on that card or create a new one:
const tokenOptions: X402TokenOptions = {
  scheme: 'nvm:card-delegation',
  delegationConfig: {
    cardId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
    spendingLimitCents: 5000,  // Only used if a new delegation is created
    durationSecs: 604800,
  }
}

Auto-Selection

When neither cardId nor delegationId is specified (and providerPaymentMethodId is omitted), the backend automatically selects the best card and delegation for the requesting API key. It finds cards accessible to the API key, looks for active delegations with remaining budget, and reuses one if available — otherwise creates a new delegation:
const tokenOptions: X402TokenOptions = {
  scheme: 'nvm:card-delegation',
  delegationConfig: {
    spendingLimitCents: 10000,
    durationSecs: 2592000,
  }
}

CardDelegationConfig Reference

FieldTypeRequiredDescription
delegationIdstringNoExisting delegation UUID to reuse
cardIdstringNoPayment method UUID to target
providerPaymentMethodIdstringNoStripe payment method ID (e.g., pm_...)
spendingLimitCentsnumberNo*Max spending in cents (for new delegations)
durationSecsnumberNo*Duration in seconds (for new delegations)
currencystringNoCurrency code, defaults to usd
merchantAccountIdstringNoStripe Connect account ID
maxTransactionsnumberNoMax transactions allowed
* Required when creating a new delegation. Ignored when reusing an existing one via delegationId.

Auto Scheme Resolution

Use resolveScheme() to auto-detect the correct scheme from plan metadata:
import { resolveScheme } from '@nevermined-io/payments'

// Auto-detect scheme from plan metadata (cached for 5 minutes)
const scheme = await resolveScheme(payments, planId)
// Returns "nvm:erc4337" for crypto plans, "nvm:card-delegation" for fiat plans

// Explicit override
const scheme = await resolveScheme(payments, planId, 'nvm:card-delegation')

DelegationAPI

Manage payment methods and delegations for card delegation:
import { DelegationAPI, PaymentMethodSummary } from '@nevermined-io/payments'

const delegationApi = DelegationAPI.getInstance(payments.options)

// List enrolled payment methods
const methods: PaymentMethodSummary[] = await delegationApi.listPaymentMethods()

for (const method of methods) {
  console.log(`${method.brand} ****${method.last4} (expires ${method.expMonth}/${method.expYear})`)
  if (method.allowedApiKeyIds) {
    console.log(`  Restricted to API keys: ${method.allowedApiKeyIds.join(', ')}`)
  }
}

Update Payment Method

Restrict a card to specific NVM API Keys so only designated agents can use it:
await delegationApi.updatePaymentMethod('pm-uuid-here', {
  alias: 'Production Card',
  allowedApiKeyIds: ['sk-agent-1', 'sk-agent-2'],  // Only these API keys can use this card
})

// Remove restrictions (any API key can use the card)
await delegationApi.updatePaymentMethod('pm-uuid-here', {
  allowedApiKeyIds: null,
})

List Delegations

Retrieve all delegations for the authenticated user:
const { delegations, totalResults } = await delegationApi.listDelegations()

console.log(`Total delegations: ${totalResults}`)
for (const d of delegations) {
  console.log(`${d.delegationId} - ${d.status} (${d.remainingBudgetCents} cents remaining)`)
  if (d.apiKeyId) {
    console.log(`  Restricted to API key: ${d.apiKeyId}`)
  }
}

DelegationListResponse Fields

listDelegations() resolves to a DelegationListResponse object:
FieldTypeDescription
delegationsDelegationSummary[]Array of delegation records
totalResultsnumberTotal number of delegations
pagenumberCurrent page number
offsetnumberOffset into the full result set

DelegationSummary Fields

Each item in the delegations array is a DelegationSummary:
FieldTypeDescription
delegationIdstringUnique delegation identifier
providerstringPayment provider (e.g., stripe)
providerPaymentMethodIdstringProvider-side payment method ID
statusstringDelegation status (Active, Revoked, etc.)
spendingLimitCentsstringMaximum spendable amount in cents
amountSpentCentsstringAmount already spent in cents
remainingBudgetCentsstringRemaining budget in cents
currencystringCurrency code (e.g., usd)
transactionCountnumberNumber of transactions made
expiresAtstringExpiry timestamp (ISO 8601)
createdAtstringCreation timestamp (ISO 8601)
apiKeyIdstring | nullAPI key this delegation is restricted to, or null if unrestricted

PaymentMethodSummary Fields

FieldTypeDescription
idstringPayment method ID (e.g., pm_...)
brandstringCard brand (e.g., visa, mastercard)
last4stringLast 4 digits of the card number
expMonthnumberCard expiration month
expYearnumberCard expiration year
allowedApiKeyIdsstring[] | nullAPI keys allowed to use this card (null = unrestricted)

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
}

Card-Delegation Token Structure

For fiat plans using nvm:card-delegation, the token contains a JWT-based delegation authorization:
{
  "x402Version": 2,
  "accepted": {
    "scheme": "nvm:card-delegation",
    "network": "stripe",
    "planId": "plan-123",
    "extra": {
      "version": "1",
      "agentId": "did:nv:agent-456"
    }
  },
  "payload": {
    "token": "eyJhbGciOiJSUzI1NiIs...",
    "authorization": {
      "from": "0xSubscriberAddress",
      "sessionKeys": [{ "id": "redeem", "data": "0xabc123..." }]
    }
  },
  "extensions": {}
}

Token Components

  • x402Version: Protocol version (2 for current spec)
  • accepted: Payment method specification
    • scheme: nvm:erc4337 for crypto or nvm:card-delegation for fiat
    • network: eip155:84532 (Base Sepolia) for crypto, stripe for fiat
    • planId: The payment plan being used
    • extra: Additional metadata (version, agentId, etc.)
  • payload: Payment authorization
    • signature (erc4337): Cryptographic proof of payment authorization
    • token (card-delegation): Signed JWT encoding the delegation claims
    • 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(`Payer: ${verification.payer}`)
} else {
  console.log(`✗ Verification failed: ${verification.invalidReason}`)
}

Verification Response

interface VerifyPermissionsResult {
  isValid: boolean           // True if token is valid
  invalidReason?: string     // Reason for invalidity (if isValid is false)
  payer?: string             // Address of the payer's wallet
  agentRequestId?: string    // Agent request ID for observability tracking (Nevermined extension)
  urlMatching?: string       // URL pattern that matched the endpoint (Nevermined extension)
  agentRequest?: StartAgentRequest  // Agent request context for observability (Nevermined extension)
}

interface StartAgentRequest {
  agentRequestId: string     // Unique request identifier
  agentName: string          // Name of the agent
  agentId: string            // Agent identifier
  balance: PlanBalance       // Current plan balance (planId, balance, pricePerCredit, etc.)
  urlMatching: string        // URL pattern that was matched
  verbMatching: string       // HTTP verb that was matched
  batch: boolean             // Whether this is a batch request
}

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. The scheme and network vary by plan type: Crypto plan:
{
  "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": {}
}
Fiat plan:
{
  "x402Version": 2,
  "resource": {
    "url": "/api/v1/tasks",
    "description": "Task execution API",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "nvm:card-delegation",
      "network": "stripe",
      "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'

// Scheme auto-detected from plan metadata when omitted
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 for nvm:erc4337)
    description: 'Task execution',  // Description
    scheme: 'nvm:erc4337'           // Optional: omit to auto-detect from plan metadata
  }
)
When scheme is set to 'nvm:card-delegation', the network is automatically set to 'stripe'.

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
  8. Restrict Cards to API Keys: When running multiple agents, restrict each card to specific NVM API Keys using allowedApiKeyIds to prevent unauthorized spending
  9. Reuse Delegations: Pass delegationId to reuse existing delegations instead of creating new ones on each request — this avoids delegation sprawl and keeps spending consolidated

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