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).
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']
# 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¶
| Field | Type | Description |
|---|---|---|
is_valid |
bool |
Whether verification passed |
subscriber_address |
str |
Subscriber's wallet address |
plan_id |
str |
Plan being used |
balance |
int |
Current credit balance |
error |
str |
Error 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¶
| Field | Type | Description |
|---|---|---|
success |
bool |
Whether settlement succeeded |
credits_redeemed |
int |
Credits that were burned |
tx_hash |
str |
Blockchain transaction hash |
remaining_balance |
int |
Credits 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¶
-
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