Framework & Tooling Integration Guides · 9 min read · Jul 2, 2026

A Python Quickstart for Paying DimeBox With x402

Every paid DimeBox route speaks x402 the same way: call it once with no payment, get back a 402 describing exactly what's owed, sign that, retry. This post walks through doing that in Python end to end — not inside an agent, not inside a framework, just the raw calls — so you have a working baseline before building anything on top of it.

Install

Pin your versions; the x402 ecosystem is still moving fast enough that "latest" is a bad default for anything you intend to run more than once:

uv add "x402[httpx,evm]"
# or: pip install "x402[httpx,evm]"

This pulls in the EVM-specific exact-scheme support alongside the base client — see what the exact scheme actually is if you want the mechanics behind what this package does for you.

Load a signer, never hardcode a key

import os
from eth_account import Account

signer = Account.from_key(os.environ["EVM_PRIVATE_KEY"])

Decide which network you're signing for before writing another line — Base Sepolia vs Base mainnet covers the tradeoff. Everything below uses eip155:84532 (Sepolia); swap to eip155:8453 once you're ready for real USDC.

Register the scheme and build the pay-retry loop

import base64, json, httpx
from x402 import x402Client
from x402.mechanisms.evm.exact import ExactEvmScheme

client = x402Client()
client.register("eip155:84532", ExactEvmScheme(signer=signer))

async def pay_and_call(http: httpx.AsyncClient, method: str, url: str, **kwargs) -> httpx.Response:
    resp = await http.request(method, url, **kwargs)
    if resp.status_code == 402:
        payment_required = json.loads(base64.b64decode(resp.headers["PAYMENT-REQUIRED"]))
        payload = await client.create_payment_payload(payment_required)
        signature = base64.b64encode(json.dumps(payload).encode()).decode()
        headers = {**kwargs.pop("headers", {}), "PAYMENT-SIGNATURE": signature}
        resp = await http.request(method, url, headers=headers, **kwargs)
    return resp

This is the same loop shown in DimeBox's own quickstart, pulled out into a helper you can reuse across every paid route instead of copy-pasting the 402 branch each time.

A full provision-then-send run

Provisioning a free inbox never hits the 402 branch at all — it's unauthenticated outright. Sending from it does:

async def main():
    async with httpx.AsyncClient() as http:
        provision = await http.post("https://dimebox.app/api/inboxes")
        provision.raise_for_status()
        inbox = provision.json()
        slug = inbox["data"]["slug"]
        read_token = inbox["read_token"]  # shown exactly once - save it now

        send = await pay_and_call(
            http, "POST",
            f"https://dimebox.app/api/inboxes/{slug}/send",
            json={"to": "[email protected]", "purpose": "follow up after our demo call"},
        )
        send.raise_for_status()
        print(send.status_code, send.json())  # 202 - queued for delivery

import asyncio
asyncio.run(main())

A synchronous version, if you don't want asyncio

Not every script is already async, and there's no requirement that it become one just to pay DimeBox. httpx has a synchronous client with the same method names, and the x402 client's payload creation can usually be awaited from a small sync wrapper if the installed version requires it — check whichever is current for your installed package. The shape of the loop doesn't change, only the keyword:

import asyncio

def pay_and_call_sync(http: httpx.Client, method: str, url: str, **kwargs) -> httpx.Response:
    resp = http.request(method, url, **kwargs)
    if resp.status_code == 402:
        payment_required = json.loads(base64.b64decode(resp.headers["PAYMENT-REQUIRED"]))
        payload = asyncio.run(client.create_payment_payload(payment_required))
        signature = base64.b64encode(json.dumps(payload).encode()).decode()
        headers = {**kwargs.pop("headers", {}), "PAYMENT-SIGNATURE": signature}
        resp = http.request(method, url, headers=headers, **kwargs)
    return resp

This trades a small amount of elegance (spinning up an event loop just for the payment step) for not having to restructure an otherwise synchronous script around one call.

When the retry also comes back 402

A second 402 after you've already signed and retried means something more specific than "payment didn't go through" — the signed amount no longer matches what the route expects, the signature's validity window expired before the retry reached the server, or whatever executes settlement — a facilitator, in DimeBox's case — couldn't be reached. None of these are something a tighter retry loop fixes on its own; they're worth logging distinctly from a first-attempt 402, which is just the protocol working as designed. x402's threat model covers what a persistent second failure might actually indicate.

Reading what you get back

A successful draft or send doesn't return the signer's wallet address back to you by accident — the response includes a paid_by field precisely so you can confirm which address the payment settled from, useful mostly for reconciling logs rather than anything your agent needs at runtime. Log it anyway; it's the cheapest audit trail you'll get for free.

Where this fits next

This loop is the one piece every Python integration in this pillar builds on, whether that's wrapping it as a LangChain tool or running it unattended from a cron job. Once it's working against Sepolia, testing the full send/receive cycle end to end is the natural next step before pointing any of this at mainnet.

New to agent email? Start with Email for Agents: The Complete Guide to Giving AI Agents a Real Inbox for the full picture.