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

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
)

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! Subscriber: {verification.subscriber_address}")
    print(f"Balance: {verification.balance}")
else:
    print(f"Invalid: {verification.error}")

Verification Response

FieldTypeDescription
is_validboolWhether verification passed
subscriber_addressstrSubscriber’s wallet address
plan_idstrPlan being used
balanceintCurrent credit balance
errorstrError message if invalid

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.tx_hash}")
    print(f"Remaining: {settlement.remaining_balance}")
else:
    print(f"Settlement failed: {settlement.error}")

Settlement Response

FieldTypeDescription
successboolWhether settlement succeeded
credits_redeemedintCredits that were burned
tx_hashstrBlockchain transaction hash
remaining_balanceintCredits remaining

Payment Required Object

The X402PaymentRequired object specifies what payment is required:
from payments_py.x402.types import X402PaymentRequired, X402Scheme

payment_required = X402PaymentRequired(
    x402_version=2,
    accepts=[
        X402Scheme(
            scheme="nvm:erc4337",
            network="eip155:84532",  # Base Sepolia
            plan_id="your-plan-id"
        )
    ],
    extensions={}
)

Using the Helper

from payments_py.x402.helpers import build_payment_required

# Simpler way to build payment required
payment_required = build_payment_required(
    plan_id="your-plan-id",
    endpoint="https://api.example.com/tasks",
    agent_id="agent-123",
    http_verb="POST"
)

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.error,
            '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

┌─────────────┐                  ┌─────────────┐                  ┌─────────────┐
│  Subscriber │                  │    Agent    │                  │  Nevermined │
└──────┬──────┘                  └──────┬──────┘                  └──────┬──────┘
       │                                │                                │
       │ GET /api/process               │                                │
       │ (no token)                     │                                │
       │ ─────────────────────────────► │                                │
       │                                │                                │
       │ ◄───────────────────────────── │                                │
       │ 402 Payment Required           │                                │
       │ {paymentRequired: {...}}       │                                │
       │                                │                                │
       │ get_x402_access_token()        │                                │
       │ ─────────────────────────────────────────────────────────────► │
       │                                │                                │
       │ ◄───────────────────────────────────────────────────────────── │
       │ {accessToken: "..."}           │                                │
       │                                │                                │
       │ GET /api/process               │                                │
       │ payment-signature: <token>     │                                │
       │ ─────────────────────────────► │                                │
       │                                │                                │
       │                                │ verify_permissions()           │
       │                                │ ─────────────────────────────► │
       │                                │                                │
       │                                │ ◄───────────────────────────── │
       │                                │ {isValid: true}                │
       │                                │                                │
       │                                │ [process request]              │
       │                                │                                │
       │                                │ settle_permissions()           │
       │                                │ ─────────────────────────────► │
       │                                │                                │
       │                                │ ◄───────────────────────────── │
       │                                │ {success: true}                │
       │                                │                                │
       │ ◄───────────────────────────── │                                │
       │ 200 OK {result: ...}           │                                │
       │                                │                                │

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