Implementing an x402 Payment Client in Python
You’re building a Python agent that needs to call paid APIs autonomously — no human to click “approve payment” mid-run. The x402 protocol exists exactly for this, and wiring it up in Python takes less than 100 lines of code once you understand the handshake.

Quick answer: A Python x402 client implementation intercepts HTTP 402 responses, parses the
X-Paymentheader for amount and destination, submits payment using a pre-configured credential, then retries the original request with aX-Payment-Receiptheader. The full pattern — detect, pay, retry — can be encapsulated in a single session wrapper that any agent framework can use transparently.
How x402 Actually Works (The Three-Step Handshake)
x402 is a detect-pay-retry loop, not a separate payment SDK. The server returns 402 Payment Required with headers describing what it wants. Your client pays. Your client retries with proof. Here’s the flow in plain terms:
- Client sends request → Server needs payment → Returns
402 - Server includes
X-Payment-Detailsheader: amount, currency, destination, expiry - Client settles payment → Gets a receipt token
- Client retries the same request with
X-Payment-Receipt: <token> - Server validates receipt → Returns
200with the actual response
The protocol is intentionally minimal. It doesn’t care whether you’re paying in USDC, fiat IOUs, or ATXP credits. That’s the settlement layer’s problem.
Setting Up Your Python x402 Client Implementation
Start with a session class that wraps requests and intercepts 402 responses automatically.
# x402_client.py
import requests
from dataclasses import dataclass
from typing import Optional
@dataclass
class PaymentCredential:
handle: str # e.g. "@myagent.atxp"
secret: str # signing secret or API key
spending_cap: float # hard limit in USD
spent: float = 0.0
class X402Client:
def __init__(self, credential: PaymentCredential, max_retries: int = 1):
self.credential = credential
self.max_retries = max_retries
self.session = requests.Session()
def get(self, url: str, **kwargs) -> requests.Response:
return self._request("GET", url, **kwargs)
def post(self, url: str, **kwargs) -> requests.Response:
return self._request("POST", url, **kwargs)
def _request(self, method: str, url: str, **kwargs) -> requests.Response:
response = self.session.request(method, url, **kwargs)
if response.status_code == 402:
receipt = self._handle_payment(response)
if receipt:
kwargs.setdefault("headers", {})
kwargs["headers"]["X-Payment-Receipt"] = receipt
response = self.session.request(method, url, **kwargs)
return response
This keeps all payment logic out of your agent’s reasoning loop. The agent calls client.get(url) and gets back a 200 — it never sees the 402.
Parsing Payment Details and Enforcing Spending Limits
Parse the X-Payment-Details header before committing any payment — never trust raw amounts blindly.
def _handle_payment(self, response: requests.Response) -> Optional[str]:
details_header = response.headers.get("X-Payment-Details")
if not details_header:
raise ValueError("402 response missing X-Payment-Details header")
details = self._parse_payment_details(details_header)
amount = float(details["amount"])
currency = details.get("currency", "USD")
# Enforce spending cap
if self.credential.spent + amount > self.credential.spending_cap:
raise RuntimeError(
f"Spending cap exceeded: cap={self.credential.spending_cap}, "
f"spent={self.credential.spent}, requested={amount}"
)
# Validate currency allowlist
if currency not in ("USD", "USDC"):
raise ValueError(f"Unsupported currency: {currency}")
# Submit payment — replace with your settlement layer
receipt = self._settle(details)
self.credential.spent += amount
return receipt
def _parse_payment_details(self, header: str) -> dict:
# Simple key=value parser; real implementations may use JSON
parts = {}
for segment in header.split(";"):
segment = segment.strip()
if "=" in segment:
k, v = segment.split("=", 1)
parts[k.strip()] = v.strip()
return parts
The spending cap check is non-negotiable. An agent loop that calls a paid endpoint repeatedly can drain a balance in seconds if nothing stops it. Hard-coding the check inside _handle_payment means it runs on every payment attempt, regardless of which framework is driving the agent.
Thinking about production-grade agent payment accounts with built-in spending caps, revocation, and audit logs? ATXP gives every agent its own payment identity — see how it works at atxp.ai.
Wiring Up the Settlement Layer
The _settle method is where x402 meets your actual payment rail. For development, stub it out:
def _settle(self, details: dict) -> str:
# Stub: replace with ATXP credits, Stripe, or stablecoin settlement
destination = details.get("destination", "unknown")
amount = details.get("amount", "0")
print(f"[x402] Paying {amount} to {destination}")
# Return a mock receipt token
return f"rcpt_{destination}_{amount}_mock"
For production with ATXP credits, swap in the ATXP payment endpoint — the agent’s handle and secret authenticate the charge against its pre-funded credit balance, and the API returns a signed receipt token you pass back as X-Payment-Receipt.
Using the Client in an Agent Loop
Drop the X402Client anywhere you’d use a requests.Session. Here’s a minimal agentic loop:
from x402_client import X402Client, PaymentCredential
cred = PaymentCredential(
handle="@researcher.atxp",
secret="sk_live_...",
spending_cap=5.00, # $5 hard cap per run
)
client = X402Client(credential=cred)
# Agent fetches paid data source — 402 handling is invisible
response = client.get("https://api.dataprovider.example/v1/market-data")
print(response.json())
print(f"Total spent: ${cred.spent:.4f}")
For LangChain, wrap this in a Tool:
from langchain.tools import tool
@tool
def fetch_paid_endpoint(url: str) -> str:
"""Fetch a URL that may require x402 payment."""
response = client.get(url)
response.raise_for_status()
return response.text
The agent reasons about what to fetch. The tool handles whether it costs money. Clean separation.
Protocol Comparison: x402 vs Alternatives
| Protocol | Transport | Agent-native | Micropayment support | Human approval required |
|---|---|---|---|---|
| x402 | HTTP headers | Yes | Yes | No |
| Stripe ACP | REST + webhooks | Partial | Limited | Often |
| Google AP2 | gRPC / REST | Partial | Limited | Often |
| Manual API keys | N/A | No | No | Yes |
x402 wins for agent-to-API payments because the entire negotiation happens in the HTTP layer — no out-of-band webhook, no UI redirect, no human in the loop.
Conclusion
A Python x402 client implementation comes down to three things: intercept 402, validate payment terms against your agent’s spending cap, settle and retry. The pattern is portable across every Python agent framework, and keeping payment logic in a dedicated client class means your agent’s reasoning stays clean.
The harder problem isn’t writing the client — it’s giving each agent isolated credentials so one runaway agent can’t drain a shared account. That’s the blast radius problem, and it’s why per-agent payment identities matter in production.