x402 V2 shipped with three major protocol upgrades: dynamic payTo routing, wallet-based session identity, and a fully modular SDK architecture. Each change unlocks powerful new capabilities for AI agent payments. Each also introduces attack vectors that didn't exist in V1.
I spent the last two weeks stress-testing the V2 migration path for PaySentry, our payment control plane for autonomous agents. Here's what you need to know about V2's security model — and how to enforce spending policies at the middleware layer before your agent loses money.
What x402 V2 Actually Changed
Quick context: x402 is the HTTP-native payment protocol built on the 402 Payment Required status code. AI agents use it to pay for API calls directly inside HTTP flows — no accounts, no OAuth, no subscription management. Coinbase launched it in 2025, Cloudflare integrated it into Workers, and it's now processing millions of agent payment flows per month.
V2 is a ground-up rearchitecture. Three changes matter for security:
1. Dynamic payTo Routing
The payTo field is no longer static. In V1, your agent paid a fixed wallet address per API endpoint. In V2, the server can specify a different recipient address on every single request.
Why this exists: Multi-tenant marketplaces where the payment recipient changes based on the underlying provider. Think: an AI agent calling a marketplace API where the service provider varies by query.
The risk: Your agent trusts the server to tell it where to send money. If the server is compromised or malicious, it can return an attacker-controlled address. Your agent pays it. The response looks valid (cached data, plausible content). No immediate red flags.
2. Wallet-Based Session Identity
V2 introduces session mechanisms using CAIP-122 (Sign-In-With-X). After the first payment, your agent gets a session token that grants repeated access without repayment.
Why this exists: Agents making repeated calls to the same endpoint (chat completions, data feeds) shouldn't pay every single time. Sessions reduce transaction overhead.
The risk: Session tokens become high-value targets. If an attacker captures a session token (via logging, middleware, or a compromised facilitator), they get free access to every resource the agent has already paid for. No additional payment required. Indefinite access if there's no expiry.
3. Modular SDK Architecture
The reference SDK was rewritten as a plugin system. Chains, assets, and payment schemes are now registered through @x402/* npm packages. The core is extractable; everything else is opt-in.
Why this exists: Extensibility. New chains and payment rails can be added without modifying core protocol code.
The risk: Supply chain attacks. A malicious plugin registered as a "payment scheme" operates inside the payment flow with access to transaction data, amounts, and potentially wallet keys. The attack surface isn't just the core SDK anymore — it's every plugin you install.
The Attack Vectors V2 Creates
Let's walk through each risk with concrete attack scenarios.
Attack 1: Recipient Address Manipulation (Dynamic Routing)
Scenario: Your agent calls a marketplace API that uses dynamic routing. The API is supposed to route payments to different service providers based on the query. On request #127, the server returns a payTo address that doesn't belong to any legitimate provider — it's the attacker's wallet.
Your agent:
- Receives the payment requirement with the malicious
payToaddress - Signs and submits the transaction (no validation)
- Gets back a 200 response with seemingly valid data (maybe cached, maybe generated)
- Continues execution — nothing looks wrong
Impact: Funds sent to attacker. The agent's application logic sees a successful API call and moves on.
Why V1 didn't have this: Static addresses. You validated the recipient once during integration and never thought about it again.
Mitigation: Recipient allowlists. Before your agent pays, check that the payTo address is on an explicit allowlist for that endpoint.
import { PaySentryX402Adapter } from '@paysentry/x402';
import { PolicyEngine, blockRecipient } from '@paysentry/control';
const policyEngine = new PolicyEngine();
// Define allowed recipients per endpoint
policyEngine.loadPolicy({
id: 'marketplace-recipients',
name: 'Marketplace Recipient Allowlist',
enabled: true,
rules: [
// Block any recipient NOT on the allowlist
blockRecipient('*', { unless: [
'eip155:8453/0xABC...', // Provider A treasury
'eip155:8453/0xDEF...', // Provider B treasury
]}),
],
budgets: [],
});
const adapter = new PaySentryX402Adapter(
{ policyEngine },
{ abortOnPolicyDeny: true }, // Abort the x402 flow if policy denies
);
When the agent attempts to pay the malicious address, the policy engine blocks it before the transaction is signed.
Attack 2: Session Token Theft (Session Identity)
Scenario: Your agent pays for access to an API and receives a session token. The token is logged to your observability platform (accidentally included in a trace). An attacker with access to your logs extracts the token and uses it to make API calls on your behalf — for free, indefinitely.
Impact: Unauthorized API access. If there's no session budget cap, the attacker can consume resources until the session expires (if it ever does).
Why V1 didn't have this: No session mechanism. Every request required a fresh payment.
Mitigation: Session-scoped budgets and aggressive TTLs. Even with a valid session token, enforce a maximum spend per session window and expire sessions after 15-30 minutes.
import { PaySentryX402Adapter } from '@paysentry/x402';
import { PolicyEngine, budgetPerSession } from '@paysentry/control';
const policyEngine = new PolicyEngine();
policyEngine.loadPolicy({
id: 'session-budgets',
name: 'Session Budget Limits',
enabled: true,
rules: [],
budgets: [
budgetPerSession({
maxAmount: 5.00, // $5 max spend per session
currency: 'USDC',
windowMs: 30 * 60 * 1000, // 30-minute session window
}),
],
});
const adapter = new PaySentryX402Adapter({ policyEngine });
If an attacker steals a session token, they can only spend up to the session budget before it's blocked. The 30-minute TTL limits exposure window.
Attack 3: Malicious Plugin Injection (Modular SDK)
Scenario: You install a new chain plugin from npm: @x402/facilitator-arbitrum. Looks legitimate. The package description matches the official pattern. Two weeks later, you discover it was a typosquat (@x4O2/facilitator-arbitrum — note the letter "O" instead of zero). The plugin has been intercepting transaction data and exfiltrating wallet keys.
Impact: Complete wallet compromise. Every transaction made through that facilitator is potentially logged by the attacker.
Why V1 didn't have this: Monolithic SDK. No plugin system.
Mitigation: Pin exact versions, verify package provenance, and audit every @x402/* dependency before installation.
# Verify npm package provenance before installing
npm view @x402/facilitator-arbitrum
# Check publisher identity
npm owner ls @x402/facilitator-arbitrum
# Pin exact version in package.json (no ^ or ~)
{
"dependencies": {
"@x402/core": "2.1.0",
"@x402/facilitator-base": "2.1.0"
}
}
Treat payment plugins like you'd treat dependencies that access secrets. Audit before install, monitor for unexpected updates, and run in isolated contexts where possible.
Building a V2 Security Policy
The pattern across all these risks: V2 gives agents more flexibility, so agents need more constraints. Recipient allowlists, session budgets, and plugin auditing aren't optional — they're the minimum.
Here's a complete policy configuration using PaySentry's declarative policy engine:
import { PaySentryX402Adapter } from '@paysentry/x402';
import { PolicyEngine, blockAbove, blockRecipient } from '@paysentry/control';
import { SpendTracker } from '@paysentry/observe';
// Initialize core engines
const policyEngine = new PolicyEngine();
const spendTracker = new SpendTracker();
// Global spending policy
policyEngine.loadPolicy({
id: 'global-limits',
name: 'Global Transaction Limits',
enabled: true,
rules: [
blockAbove(1.00, 'USDC'), // Block any transaction >$1
],
budgets: [
{
id: 'daily-limit',
maxAmount: 50.00, // $50/day max across all APIs
currency: 'USDC',
windowMs: 24 * 60 * 60 * 1000,
},
],
});
// Per-endpoint policies
policyEngine.loadPolicy({
id: 'openai-policy',
name: 'OpenAI API Spending',
enabled: true,
rules: [
blockAbove(0.10, 'USDC'), // $0.10 max per completion
blockRecipient('*', {
unless: ['eip155:8453/0xOPENAI_OFFICIAL_ADDRESS'],
}),
],
budgets: [
{
id: 'openai-hourly',
maxAmount: 5.00,
currency: 'USDC',
windowMs: 60 * 60 * 1000, // $5/hour for OpenAI
},
],
scope: { recipient: 'api.openai.com' },
});
// Session security
policyEngine.loadPolicy({
id: 'session-security',
name: 'Session Budget Limits',
enabled: true,
rules: [],
budgets: [
{
id: 'session-cap',
maxAmount: 5.00,
currency: 'USDC',
windowMs: 30 * 60 * 1000, // 30-minute session window
scope: 'session',
},
],
});
// Create the adapter
const adapter = new PaySentryX402Adapter(
{ policyEngine, spendTracker },
{
abortOnPolicyDeny: true, // Reject payments that violate policy
circuitBreaker: {
failureThreshold: 5, // Open circuit after 5 failures
recoveryTimeoutMs: 30000, // 30s recovery window
},
},
);
// Register with your x402 server
adapter.withLifecycleHooks(x402Server);
This configuration:
- Blocks any transaction over $1 (global cap)
- Enforces a $50 daily spending limit across all endpoints
- Restricts OpenAI payments to $0.10 per request and $5/hour total
- Only allows payments to explicitly allowlisted recipient addresses
- Caps session spending at $5 per 30-minute window
- Opens a circuit breaker after 5 consecutive payment failures (protects against cascading failures)
Every policy is evaluated before the transaction is signed. If any rule blocks the transaction, the x402 flow aborts and your agent never pays.
The "79 Tests" Problem
Here's something nobody talks about: x402 security isn't just about writing policies. It's about testing that they actually work under adversarial conditions.
PaySentry has 79 test cases covering policy enforcement, circuit breaker state transitions, session budget tracking, and x402 lifecycle hook integration. That sounds like a lot until you realize how many edge cases exist:
- What happens when a policy blocks a transaction mid-flight?
- Does the circuit breaker correctly transition from half-open to closed after a successful probe?
- Are session budgets enforced across facilitator retries?
- Do recipient allowlists work for CAIP-standardized addresses with chain prefixes?
If you're building your own policy layer, you need test coverage for:
-
Policy evaluation logic — Does
blockAbove(1.00)actually block at $1.01? - State management — Do rolling window budgets reset correctly?
- x402 integration — Do lifecycle hooks run in the right order?
-
Failure modes — What happens when the policy engine crashes during
onBeforeVerify?
Testing payment security is not optional. One missed edge case and your agent burns through its budget in seconds.
V2 Migration Checklist
Before you deploy V2 in production:
- ✅ Recipient allowlists configured for every API endpoint your agent calls
- ✅ Session TTLs defined — 15-30 minutes for most agent workloads
- ✅ Session budget caps enforced — max spend per session, not just per transaction
- ✅ Global daily spending limit active — hard cap across all endpoints
- ✅ Plugin versions pinned — exact versions, no semver ranges, provenance verified
- ✅ Circuit breaker configured — prevent cascading failures to payment facilitators
- ✅ Monitoring hooked up — log every policy denial, every failed payment, every new recipient address
- ✅ Test coverage for adversarial scenarios — test that policies actually block malicious transactions
The Bottom Line
x402 V2 is a significant step forward for agent payments. Dynamic routing enables marketplaces. Session identity reduces transaction overhead. Modular SDKs make the protocol extensible.
But every new capability is a new attack surface:
- Dynamic
payTo→ recipient manipulation - Session tokens → token theft and session hijacking
- Plugin architecture → supply chain attacks
The fix isn't to avoid V2. It's to match V2's flexibility with equally granular security policies. Recipient allowlists, session budgets, and plugin auditing are table stakes.
Your agent is spending real money on every API call. Treat its payment security like you'd treat your own wallet — because in production, it is your wallet.
PaySentry is an open-source payment control plane for AI agents. It enforces spending policies, tracks transactions, and provides circuit breakers for x402, Stripe, and custom payment protocols. GitHub | npm
Follow me for more on AI agent security, payment protocol internals, and production deployment war stories.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.