x402 fixed something fundamental about the web. For years, HTTP status code 402 Payment Required was reserved and unused - a placeholder waiting for a protocol. Coinbase's x402 finally gave it one: a clean, chain-agnostic, HTTP-native way for autonomous callers to pay for resources.
x402 is minimal by design. That's a feature. It specifies how payment gets negotiated and settled over HTTP, and it stops there. But the moment you ship an autonomous AI agent that can use x402, you hit a layer that x402 intentionally doesn't address: policy.
This post is about that missing layer - why x402 needs one, and how xBPP composes with it to give you a complete stack for autonomous agent payments.
WHAT X402 DOES (AND DELIBERATELY DOESN'T)
x402 is scoped tightly around a single concern: machine-to-machine payment negotiation on HTTP.
What's in scope
- ▸Payment challenge via HTTP status 402
- ▸Stablecoin settlement on supported chains
- ▸Payment headers and proof format
- ▸Retry semantics with the payment proof
Deliberately out of scope
- ▸Whether the caller should pay
- ▸Budget enforcement across many transactions
- ▸Counterparty evaluation
- ▸Escalation to humans
- ▸Audit trails beyond the proof itself
These exclusions aren't oversights - they're the right design call for a minimal payment rail. Policy concerns vary dramatically across deployments. Pushing policy into x402 would bloat the protocol and fracture implementations. Keeping it separate keeps x402 clean and lets policy evolve independently.
WHAT HAPPENS WHEN YOU SKIP THE POLICY LAYER
Here's what "just use x402" looks like in production:
// v1 - naive x402 agent
async function agentCall(url: string) {
const res = await fetchWithX402(url)
return res.json()
}Clean. Pays automatically. Works great for the first week. Then:
- ▸The agent starts paying a flaky endpoint on every retry and burns $80 in a morning
- ▸A prompt injection tricks the agent into paying an unrelated URL
- ▸Finance asks what the agent spent yesterday and you have to grep logs
- ▸You hardcode a if (amount > 10) throw - which fires on a legitimate $11 call the next day
- ▸You realize you need escalation, not just blocking
- ▸You start writing a second agent and copy the whole pile of ad-hoc rules into it
This is how every team that ships agents ends up with a tangled policy layer they wrote by accident. The alternative is to put a real policy layer in from day one, and the good news is it's about five lines of code.
XBPP: THE POLICY LAYER, COMPOSED
xBPP is a policy engine designed to sit in front of any payment rail. It takes a transaction request and a policy, evaluates them against 12 built-in checks, and returns a verdict: ALLOW, BLOCK, or ESCALATE. The policy is declarative JSON. The SDK is 600 lines with zero runtime dependencies. It's Apache 2.0 and works with any x402 implementation.
Before: raw x402
async function agentCall(url: string) {
const res = await fetchWithX402(url)
return res.json()
}After: x402 + xBPP
import { evaluate } from '@vanar/xbpp'
import policy from './policies/research-agent.json'
async function agentCall(url: string) {
const probe = await fetch(url)
if (probe.status === 402) {
const amount = probe.headers.get('x-payment-amount')
const verdict = evaluate(
{ amount: Number(amount), currency: 'USDC', recipient: url },
policy
)
switch (verdict.decision) {
case 'BLOCK':
throw new PolicyError(verdict.reasons)
case 'ESCALATE':
await humanApproval.request(request, verdict)
break
case 'ALLOW':
return fetchWithX402(url, { policy: verdict })
}
}
return probe.json()
}Seven lines of additional logic. Policy lives in a JSON file you can version and ship independently. x402 handles payment. xBPP handles policy. Neither knows about the other.
THE CLEAN DIVISION OF CONCERNS
This is what makes the composition work: x402 and xBPP own completely separate concerns and communicate through a single small surface area.
| Concern | x402 | xBPP |
|---|---|---|
| HTTP 402 negotiation | ✓ | - |
| Stablecoin settlement | ✓ | - |
| Payment proof | ✓ | - |
| Retry semantics | ✓ | - |
| Policy evaluation | - | ✓ |
| Budget tracking | - | ✓ |
| Counterparty checks | - | ✓ |
| Escalation routing | - | ✓ |
| Reason codes | - | ✓ |
| Audit trail | - | ✓ |
Both are replaceable. If a better x402 implementation ships tomorrow, you swap it out and xBPP doesn't notice. If you decide to write your own policy engine, xBPP's evaluation interface is small enough to reimplement in an afternoon. That's the point of an open standard.
WHAT A PRODUCTION X402 + XBPP POLICY LOOKS LIKE
Here's a real policy for an autonomous research agent that buys reports and API credits over x402:
{
"name": "research-agent-x402",
"version": "7",
"checks": [
{ "type": "hard_cap_per_transaction", "amount_usd": 250 },
{ "type": "daily_budget", "amount_usd": 1000 },
{ "type": "monthly_budget", "amount_usd": 15000 },
{ "type": "escalation_threshold", "amount_usd": 50 },
{ "type": "currency_allowlist", "currencies": ["USDC"] },
{ "type": "chain_allowlist", "chains": ["base", "base-sepolia"] },
{
"type": "counterparty_allowlist",
"patterns": [
"https://*.reports.trusted-vendor.com",
"https://api.data-provider.io/*"
]
},
{ "type": "recipient_freshness", "action": "escalate", "days": 14 },
{ "type": "velocity_limit", "max_per_hour": 20 }
]
}Every rule here is something we've seen a team need in week two of deploying an agent. Having them in one place, as data, means you can evolve them without touching agent code - and you can run the exact same policy in staging, production, and a unit test.
SERVER-SIDE X402 + XBPP
xBPP isn't just a client-side concern. x402 services - the ones receiving payments - can use xBPP on the server to evaluate incoming requests:
app.get('/premium-report', async (req, res) => {
const verdict = evaluate(
{ amount: 5.00, currency: 'USDC', caller: req.x402.caller },
serverPolicy
)
if (verdict.decision === 'BLOCK') {
return res.status(403).json({
error: 'policy_block',
reasons: verdict.reasons
})
}
// Proceed with the x402 payment challenge
res.status(402).setX402PaymentHeader({
amount: 5.00,
currency: 'USDC'
})
})This is useful for services that want to:
- ▸Rate-limit specific callers
- ▸Block known abusive patterns
- ▸Enforce tiered pricing
- ▸Audit which agents called them and why
Same engine, same policy format, different deployment location. That's the value of policy being data.
GETTING STARTED
If you're already using x402 and want to add policy:
npm install @vanar/xbppThen follow the x402 + xBPP guide for the full integration. The minimum viable setup is about 10 lines of code and takes a few minutes.