x402 Protocol¶
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:
| Scheme | Network | Use Case | Settlement |
|---|---|---|---|
nvm:erc4337 |
eip155:84532 |
Crypto payments | ERC-4337 UserOps + session keys |
nvm:card-delegation |
stripe |
Fiat/credit card | Stripe 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:
- Navigate to the permissions page
- Select your plan and agent
- Configure limits (optional)
- 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']
# With delegation config (crypto — erc4337)
from payments_py.x402 import X402TokenOptions, DelegationConfig, CreateDelegationPayload
# Pattern A — auto-create delegation
result = payments.x402.get_x402_access_token(
plan_id="your-plan-id",
agent_id="agent-id",
token_options=X402TokenOptions(
delegation_config=DelegationConfig(
spending_limit_cents=10000, # $100
duration_secs=604800 # 1 week
)
)
)
# Pattern B — explicit create + reuse across plans
delegation = payments.delegation.create_delegation(
CreateDelegationPayload(
provider="erc4337",
spending_limit_cents=10000,
duration_secs=604800
)
)
result = payments.x402.get_x402_access_token(
plan_id="your-plan-id",
agent_id="agent-id",
token_options=X402TokenOptions(
delegation_config=DelegationConfig(delegation_id=delegation.delegation_id)
)
)
Card-Delegation Token Generation¶
For fiat plans using nvm:card-delegation, pass X402TokenOptions with a DelegationConfig:
from payments_py.x402 import X402TokenOptions, DelegationConfig
# USD card delegation
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=DelegationConfig(
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']
# EUR card delegation
eur_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=DelegationConfig(
provider_payment_method_id="pm_1AbCdEfGhIjKlM",
spending_limit_cents=10000, # €100.00 (in euro cents)
duration_secs=2592000, # 30 days
currency="eur",
max_transactions=100
)
)
)
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¶
Create delegations and list enrolled payment methods:
from payments_py.x402 import CreateDelegationPayload
# Create a crypto delegation
delegation = payments.delegation.create_delegation(
CreateDelegationPayload(
provider="erc4337",
spending_limit_cents=10000,
duration_secs=604800
)
)
print(f"Delegation ID: {delegation.delegation_id}")
# List enrolled payment methods (for card delegation)
methods = payments.delegation.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:
| Field | Type | Description |
|---|---|---|
id |
str |
Payment method ID (e.g., pm_...) |
brand |
str |
Card brand (e.g., visa, mastercard) |
last4 |
str |
Last 4 digits of the card number |
exp_month |
int |
Card expiration month |
exp_year |
int |
Card 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¶
| Field | Type | Description |
|---|---|---|
is_valid |
bool |
Whether verification passed |
invalid_reason |
str |
Reason for invalidity (if is_valid is false) |
payer |
str |
Payer's wallet address |
agent_request_id |
str |
Agent 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¶
| Field | Type | Description |
|---|---|---|
success |
bool |
Whether settlement succeeded |
error_reason |
str |
Reason for failure (if success is false) |
payer |
str |
Payer's wallet address |
transaction |
str |
Blockchain transaction hash |
credits_redeemed |
str |
Credits that were burned |
remaining_balance |
str |
Credits 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¶
sequenceDiagram
participant Subscriber
participant Agent
participant Nevermined
Subscriber->>Agent: GET /api/process (no token)
Agent-->>Subscriber: 402 Payment Required<br/>{paymentRequired: {...}}
Subscriber->>Nevermined: get_x402_access_token()
Nevermined-->>Subscriber: {accessToken: "..."}
Subscriber->>Agent: GET /api/process<br/>payment-signature: token
Agent->>Nevermined: verify_permissions()
Nevermined-->>Agent: {isValid: true}
Note over Agent: Process request
Agent->>Nevermined: settle_permissions()
Nevermined-->>Agent: {success: true}
Agent-->>Subscriber: 200 OK {result: ...}
Best Practices¶
-
Always verify before processing: Don't do expensive work without verification
-
Only settle on success: Don't burn credits if processing fails
-
Use agent_request_id: Include request IDs for tracking and debugging
-
Handle 402 responses: Return proper payment required responses with scheme info
-
Cache verifications carefully: Tokens can be used multiple times until limits are reached
Error Codes¶
| Error | Description | Resolution |
|---|---|---|
invalid_token |
Token is malformed | Generate a new token |
expired_token |
Token has expired | Generate a new token |
insufficient_balance |
Not enough credits | Order more credits |
invalid_plan |
Plan ID mismatch | Use correct plan ID |
invalid_agent |
Agent ID mismatch | Use correct agent ID |
Next Steps¶
- Request Validation - More validation patterns
- MCP Integration - x402 with MCP servers