Skip to main content
This guide covers the x402 payment protocol for verifying permissions and settling payments.

Overview

x402 is a payment protocol that enables:
  • Permission Generation: Subscribers create access tokens for agents
  • Permission Verification: Agents verify tokens without burning credits
  • Permission Settlement: Agents burn credits after completing work
The protocol is named after HTTP status code 402 (Payment Required).

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 resolve_scheme().

Generate Payment Permissions

From Nevermined App

The easiest way to generate permissions is through the Nevermined App Permissions page:
  1. Navigate to the permissions page
  2. Select your plan and agent
  3. Configure limits (optional)
  4. Generate the access token

From SDK

from payments_py import Payments, PaymentOptions

payments = Payments.get_instance(
    PaymentOptions(nvm_api_key="nvm:subscriber-key", environment="sandbox")
)

# Basic token generation
result = payments.x402.get_x402_access_token(
    plan_id="your-plan-id",
    agent_id="agent-id"
)
access_token = result['accessToken']

# Advanced: with limits
result = payments.x402.get_x402_access_token(
    plan_id="your-plan-id",
    agent_id="agent-id",
    redemption_limit=100,  # Max 100 requests
    order_limit="1000000000000000000",  # Max 1 token spend
    expiration="2025-12-31T23:59:59Z"  # Expires end of year
)

Card-Delegation Token Generation

For fiat plans using nvm:card-delegation, pass X402TokenOptions with a CardDelegationConfig:
from payments_py.x402.types import X402TokenOptions, CardDelegationConfig

# Generate a card-delegation token
result = payments.x402.get_x402_access_token(
    plan_id="your-plan-id",
    agent_id="agent-id",
    token_options=X402TokenOptions(
        scheme="nvm:card-delegation",
        delegation_config=CardDelegationConfig(
            provider_payment_method_id="pm_1AbCdEfGhIjKlM",
            spending_limit_cents=10000,  # $100.00
            duration_secs=2592000,       # 30 days
            currency="usd",
            max_transactions=100
        )
    )
)
access_token = result['accessToken']

Auto Scheme Resolution

Use resolve_scheme() to auto-detect the correct scheme from plan metadata:
from payments_py.x402.resolve_scheme import resolve_scheme

# Auto-detect scheme from plan metadata (cached for 5 minutes)
scheme = resolve_scheme(payments, plan_id="your-plan-id")
# Returns "nvm:erc4337" for crypto plans, "nvm:card-delegation" for fiat plans

# Explicit override
scheme = resolve_scheme(payments, plan_id="your-plan-id", explicit_scheme="nvm:card-delegation")

DelegationAPI

List enrolled payment methods for card delegation:
from payments_py.x402.delegation_api import DelegationAPI, PaymentMethodSummary

delegation_api = DelegationAPI.get_instance(payments.options)
methods: list[PaymentMethodSummary] = delegation_api.list_payment_methods()

for method in methods:
    print(f"{method.brand} ****{method.last4} (expires {method.exp_month}/{method.exp_year})")
    # e.g., "visa ****4242 (expires 12/2027)"
PaymentMethodSummary fields:
FieldTypeDescription
idstrPayment method ID (e.g., pm_...)
brandstrCard brand (e.g., visa, mastercard)
last4strLast 4 digits of the card number
exp_monthintCard expiration month
exp_yearintCard expiration year

Token Structure

The x402 token is a base64-encoded JSON document:
{
  "payload": {
    "authorization": {
      "from": "0xSubscriberAddress",
      "planId": "plan-123",
      "agentId": "agent-456"
    },
    "sessionKey": {
      "address": "0xSessionKeyAddress",
      "permissions": ["order", "burn"],
      "limits": {
        "redemptionLimit": 100,
        "orderLimit": "1000000000000000000"
      }
    }
  },
  "signature": "0x..."
}

Verify Payment Permissions

Verification checks if a subscriber has valid permissions without burning credits:
from payments_py import Payments, PaymentOptions
from payments_py.x402.helpers import build_payment_required

payments = Payments.get_instance(
    PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox")
)

# Build the 402 Payment Required specification
payment_required = build_payment_required(
    plan_id="your-plan-id",
    endpoint="https://your-api.com/endpoint",
    agent_id="your-agent-id",
    http_verb="POST"
)

# Verify the token
verification = payments.facilitator.verify_permissions(
    payment_required=payment_required,
    x402_access_token=access_token,
    max_amount="1"  # Optional: max credits to verify
)

if verification.is_valid:
    print(f"Valid! Payer: {verification.payer}")
else:
    print(f"Invalid: {verification.invalid_reason}")

Verification Response

FieldTypeDescription
is_validboolWhether verification passed
invalid_reasonstrReason for invalidity (if is_valid is false)
payerstrPayer’s wallet address
agent_request_idstrAgent request ID for observability tracking

Settle Payment Permissions

Settlement burns credits after successfully processing a request:
# After processing the request successfully
settlement = payments.facilitator.settle_permissions(
    payment_required=payment_required,
    x402_access_token=access_token,
    max_amount="1",  # Credits to burn
    agent_request_id="request-123"  # Optional: for tracking
)

if settlement.success:
    print(f"Settled! Credits burned: {settlement.credits_redeemed}")
    print(f"Transaction: {settlement.transaction}")
    print(f"Remaining: {settlement.remaining_balance}")
else:
    print(f"Settlement failed: {settlement.error_reason}")

Settlement Response

FieldTypeDescription
successboolWhether settlement succeeded
error_reasonstrReason for failure (if success is false)
payerstrPayer’s wallet address
transactionstrBlockchain transaction hash
credits_redeemedstrCredits that were burned
remaining_balancestrCredits remaining

Payment Required Object

The X402PaymentRequired object specifies what payment is required. The scheme and network fields vary by payment type:
from payments_py.x402.types import X402PaymentRequired, X402Resource, X402Scheme, X402SchemeExtra

# Crypto plan (nvm:erc4337)
payment_required = X402PaymentRequired(
    x402_version=2,
    resource=X402Resource(
        url="https://your-api.com/endpoint",
        description="Protected endpoint"  # Optional
    ),
    accepts=[
        X402Scheme(
            scheme="nvm:erc4337",
            network="eip155:84532",  # Base Sepolia
            plan_id="your-plan-id",
            extra=X402SchemeExtra(
                http_verb="POST",  # HTTP method goes in extra, not resource
                agent_id="agent-123"  # Optional
            )
        )
    ],
    extensions={}
)

# Fiat plan (nvm:card-delegation)
payment_required_fiat = X402PaymentRequired(
    x402_version=2,
    resource=X402Resource(
        url="https://your-api.com/endpoint",
        description="Protected endpoint"
    ),
    accepts=[
        X402Scheme(
            scheme="nvm:card-delegation",
            network="stripe",
            plan_id="your-plan-id",
            extra=X402SchemeExtra(
                http_verb="POST",
                agent_id="agent-123"
            )
        )
    ],
    extensions={}
)

Using the Helpers

from payments_py.x402.helpers import build_payment_required, build_payment_required_for_plans

# Single plan (scheme auto-detected from plan metadata when omitted)
payment_required = build_payment_required(
    plan_id="your-plan-id",
    endpoint="https://api.example.com/tasks",
    agent_id="agent-123",
    http_verb="POST"
)

# Explicit scheme override
payment_required = build_payment_required(
    plan_id="your-plan-id",
    endpoint="https://api.example.com/tasks",
    agent_id="agent-123",
    http_verb="POST",
    scheme="nvm:card-delegation"  # Force fiat/Stripe scheme
)

# Multiple plans — creates one entry per plan in accepts[]
payment_required = build_payment_required_for_plans(
    plan_ids=["plan-basic", "plan-premium"],
    endpoint="https://api.example.com/tasks",
    agent_id="agent-123",
    http_verb="POST"
)
For a single plan, build_payment_required_for_plans delegates to build_payment_required internally. When scheme is omitted, the network defaults to eip155:84532 (Base Sepolia). When scheme="nvm:card-delegation", the network is automatically set to stripe.

Complete Workflow Example

from payments_py import Payments, PaymentOptions
from payments_py.x402.helpers import build_payment_required
from flask import Flask, request, jsonify

app = Flask(__name__)

# Agent's payments instance
agent_payments = Payments.get_instance(
    PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox")
)

PLAN_ID = "your-plan-id"
AGENT_ID = "your-agent-id"

@app.route('/api/process', methods=['POST'])
def process_request():
    # 1. Extract x402 token from payment-signature header
    token = request.headers.get('payment-signature', '')
    if not token:
        return jsonify({'error': 'Missing payment-signature header'}), 402

    # 2. Build payment requirement
    payment_required = build_payment_required(
        plan_id=PLAN_ID,
        endpoint=request.url,
        agent_id=AGENT_ID,
        http_verb=request.method
    )

    # 3. Verify (doesn't burn credits)
    verification = agent_payments.facilitator.verify_permissions(
        payment_required=payment_required,
        x402_access_token=token,
        max_amount="1"
    )

    if not verification.is_valid:
        return jsonify({
            'error': 'Payment required',
            'details': verification.invalid_reason,
            'paymentRequired': payment_required.model_dump()
        }), 402

    # 4. Process the request
    try:
        result = do_expensive_work(request.json)
    except Exception as e:
        # Don't settle on failure
        return jsonify({'error': str(e)}), 500

    # 5. Settle (burn credits) on success
    settlement = agent_payments.facilitator.settle_permissions(
        payment_required=payment_required,
        x402_access_token=token,
        max_amount="1"
    )

    return jsonify({
        'result': result,
        'creditsUsed': settlement.credits_redeemed,
        'remainingBalance': settlement.remaining_balance
    })

def do_expensive_work(data):
    # Your processing logic
    return {'processed': True}

if __name__ == '__main__':
    app.run(port=8080)

HTTP Flow

Best Practices

  1. Always verify before processing: Don’t do expensive work without verification
  2. Only settle on success: Don’t burn credits if processing fails
  3. Use agent_request_id: Include request IDs for tracking and debugging
  4. Handle 402 responses: Return proper payment required responses with scheme info
  5. Cache verifications carefully: Tokens can be used multiple times until limits are reached

Error Codes

ErrorDescriptionResolution
invalid_tokenToken is malformedGenerate a new token
expired_tokenToken has expiredGenerate a new token
insufficient_balanceNot enough creditsOrder more credits
invalid_planPlan ID mismatchUse correct plan ID
invalid_agentAgent ID mismatchUse correct agent ID

Next Steps