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.

Implementing an x402 Payment Client in Python

Quick answer: A Python x402 client implementation intercepts HTTP 402 responses, parses the X-Payment header for amount and destination, submits payment using a pre-configured credential, then retries the original request with a X-Payment-Receipt header. 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:

  1. Client sends request → Server needs payment → Returns 402
  2. Server includes X-Payment-Details header: amount, currency, destination, expiry
  3. Client settles payment → Gets a receipt token
  4. Client retries the same request with X-Payment-Receipt: <token>
  5. Server validates receipt → Returns 200 with 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

ProtocolTransportAgent-nativeMicropayment supportHuman approval required
x402HTTP headersYesYesNo
Stripe ACPREST + webhooksPartialLimitedOften
Google AP2gRPC / RESTPartialLimitedOften
Manual API keysN/ANoNoYes

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.

ATXP gives every AI agent its own handle, credit balance, spending cap, and revocation control — visit atxp.ai to get started.