Skip to main content
PAYMENT API

A payment API designed for AI agent code.

A small REST API. Signed webhooks. Official SDKs in five languages. Built so your agent can send and settle USDC and verify payments in a few lines of code.

Transactions view: incoming USDC payments confirmed on Base

The Blockchain0x API is deliberately small. We do not want you to read documentation for an hour to make your first payment. We want you to copy a snippet, paste your API key, and run it.

THE CORE ENDPOINTS

A small surface, built for agent code.

The payment-shaped routes are below. Webhooks, api-keys, and read-only spend-permissions round out the surface; the SDK wraps all of it.

POST/v1/paymentsSend a USDC payment from an agent wallet
GET/v1/transactions/{id}Fetch a transaction's status and hash
POST/v1/payment-requests/{id}/settleSettle an invoice with on-chain proof
GET/v1/agents/{id}Fetch an agent wallet
POST/v1/agentsCreate an agent wallet
THE SHIPPED WEBHOOK EVENTS

All signed with HMAC-SHA256.

Signature in the X-Blockchain0x-Signature header. Replay window of 5 minutes. Idempotency keys on every POST.

payment.received

USDC landed in one of your agent wallets

payment.sent

an outbound payment from your agent settled on chain

wallet.deployed

an agent's smart wallet was deployed

webhook.test

a test delivery you triggered from the dashboard

A FEW LINES

Send USDC from your agent's wallet.

PYTHON
from blockchain0x import Client

client = Client(api_key="sk_test_...")  # sk_test_ testnet, sk_live_ mainnet
tx = client.payments.create(body={
    "agentId": "agt_123",
    "to": "0xRecipient",
    "amountWei": "10000",  # USDC base units (6 decimals): 0.01 USDC
})
print(tx)  # may return 503 until the chain adapter is wired for your network
OFFICIAL SDKs

Typed clients for TypeScript and Python.

TYPESCRIPT
npm install @blockchain0x/node

Works with Node 18+. Native TypeScript types.

PYTHON
pip install blockchain0x

Python 3.9+. Sync and async clients.

FULL ENDPOINT SCHEMAS

The request bodies the SDK wraps.

These are the verified inputs for each route. Responses are shown short here; the full typed shapes live in the SDK and the API reference. Amounts are USDC base units (6 decimals).

POST/v1/payments

Send a USDC payment (payments.create). amountWei is USDC base units (6 decimals). May return 503 until the chain adapter is wired.

REQUEST
{
  "agentId": "agt_123",
  "to": "0xRecipient",
  "amountWei": "10000",
  "metadata": { "reason": "Research report" }
}
RESPONSE
{
  "txHash": "0xff5...e12"
}
POST/v1/payment-requests/{id}/settle

Settle a payment request (invoice) created in the dashboard, with on-chain proof (paymentRequests.settle). There is no create route - settle only.

REQUEST
{
  "txHash": "0xff5...e12",
  "payerAddress": "0xBuyer...",
  "amountUsdcVerified": "0.10"
}
RESPONSE
{
  "txHash": "0xff5...e12"
}
GET/v1/transactions/{id}

Fetch a single transaction by id (transactions.get).

REQUEST
(no body)
RESPONSE
{
  "id": "tx_01J9...",
  "txHash": "0xff5...e12"
}
GET/v1/agents/{id}

Fetch an agent wallet (agents.get). Its public page lives at wallet.blockchain0x.com/a/{slug}.

REQUEST
(no body)
RESPONSE
{
  "id": "agt_01J9QKE...",
  "name": "research-bot",
  "public_url": "https://wallet.blockchain0x.com/a/research-bot"
}
GET/v1/agents/{id}/spend-permissions

Read an agent's spend permissions. READ-ONLY via the API - creation and changes are dashboard-only.

REQUEST
(no body)
RESPONSE
{
  "allowance_wei": "5000000",
  "per_tx_wei": "1000000",
  "period_seconds": 86400,
  "start_at": "2026-05-15T00:00:00Z",
  "revoked_at": null
}
WEBHOOK SIGNATURE VERIFICATION

Always verify the signature before trusting the payload.

Anyone can POST to your callback URL. The signature is what proves the event actually came from us.

Every delivery carries an X-Blockchain0x-Signature header (t=<unix>,v1=<hex>) - an HMAC-SHA256 over the string `${t}.${rawBody}` with your webhook secret, inside a 5-minute replay window. In Node, webhooks.verify from @blockchain0x/node does it and returns a discriminated union you branch on. In other languages, compute the same HMAC and compare in constant time. Read the RAW body, not a parsed copy - re-serializing breaks the signature.

TYPESCRIPT (NODE + EXPRESS)
import express from "express";
import { webhooks } from "@blockchain0x/node";

const app = express();
// Capture the RAW body. The HMAC is over the exact bytes on the wire.
app.use(express.raw({ type: "application/json" }));

app.post("/webhooks/payment", (req, res) => {
  const result = webhooks.verify({
    headers: req.headers,
    rawBody: req.body, // Buffer, raw bytes
    secret: process.env.BLOCKCHAIN0X_WEBHOOK_SECRET!,
  });
  if (!result.ok) return res.status(400).json({ code: result.code });
  // result.eventType is "payment.received" | "payment.sent" | ...
  handleEvent(result);
  res.status(200).send("ok");
});
PYTHON (FLASK)
import hmac, hashlib, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["BLOCKCHAIN0X_WEBHOOK_SECRET"].encode()

@app.post("/webhooks/payment")
def receive():
    raw = request.get_data()  # RAW bytes - do not parse first
    sig = request.headers.get("X-Blockchain0x-Signature", "")
    ts = request.headers.get("X-Blockchain0x-Timestamp", "")
    # Header is "t=<unix>,v1=<hex>"; some proxies send bare hex + the ts header.
    parts = dict(p.split("=", 1) for p in sig.split(",") if "=" in p)
    t, v1 = parts.get("t", ts), parts.get("v1", sig)
    want = hmac.new(SECRET, t.encode() + b"." + raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(want, v1) or abs(time.time() - int(t)) > 300:
        abort(401)
    handle_event(request.headers.get("X-Blockchain0x-Event-Type"), request.get_json())
    return "ok", 200

The webhook secret is returned once when you create or rotate the webhook (webhooks.rotateSecret), and BLOCKCHAIN0X_WEBHOOK_SECRET is where your handler reads it. Rotating invalidates the old secret, so coordinate the rollout: set the new secret in your server's env, deploy, then rotate. The shipped events are payment.received, payment.sent, wallet.deployed, and webhook.test - branch on the event type, ignore the rest.

IDEMPOTENCY

Retry safely. The API will not pay twice.

Agent code retries. Networks drop connections. LLMs decide to call the tool twice. To make this safe, send an Idempotency-Key header on the POST. payments.create in the SDK auto-mints a stable one for you, so a retried call collapses to a single on-chain submission instead of paying twice. Override it with your own key when you want to dedupe the same logical operation across processes.

BASH
curl -X POST https://api.blockchain0x.com/v1/payments \
  -H "Authorization: Bearer $BLOCKCHAIN0X_API_KEY" \
  -H "Idempotency-Key: 8e2c3a40-pay-vendor-once" \
  -H "Content-Type: application/json" \
  -d '{ "agentId": "agt_123", "to": "0xRecipient", "amountWei": "10000" }'
  • First call: submits the payment, returns the transaction.
  • Retry with the same key: collapses to one on-chain submission instead of paying twice.
  • The SDK handles it: payments.create auto-mints a stable Idempotency-Key, so a retried call is safe out of the box.
  • Override it: pass your own key when you want to dedupe the same logical operation across processes.

The recommended pattern is to generate a UUID per logical operation in your agent code (e.g. one UUID per "pay vendor X for order Y") and reuse it across retries until the operation succeeds. The official SDKs auto-generate one for payments.create if you do not specify it.

RATE LIMITS

Read your limit off the response, not a table.

Limits scale with your plan. Rather than hard-code numbers that drift, every response tells you where you stand and the SDKs handle backoff for you.

Headers on every success response

  • X-RateLimit-Limit: your plan's steady rate
  • X-RateLimit-Remaining: how many calls left in this window
  • X-RateLimit-Reset: unix seconds when the window resets

When you hit the cap (HTTP 429)

  • Response includes Retry-After in seconds
  • The official SDKs retry on 429 and 5xx with exponential backoff, honouring Retry-After
  • Most code never handles 429 by hand because the SDK already does
X402 COMPATIBILITY

Speaks x402 fluently. Works without it too.

x402 is the open protocol for HTTP-native payments. The premise: any API that returns 402 Payment Required can advertise payment requirements in the response, and any client that understands the spec can pay and retry - no signup, no API key, no out-of-band negotiation. Blockchain0x ships real x402 packages: a pay-side client and receive-side server adapters. The only scheme today is exact-usdc, on Base.

THE 402 A SERVER RETURNS WHEN A CALLER HAS NOT PAID
HTTP/1.1 402 Payment Required
Content-Type: application/json

{
  "version": 1,
  "resource": "https://api.example.com/paid-endpoint",
  "accepts": [
    { "scheme": "exact-usdc", "network": "eip155:8453" }
  ]
}

What the agent caller does

  1. Reads the 402 body: version 1, the resource, and a non-empty accepts[] of payment requirements (scheme exact-usdc, network eip155:8453 for Base).
  2. Builds a payment and resends the request with an X-Payment: exact-usdc:<base64(json)> header.
  3. On the pay side, createX402Client({ sdk }) from @blockchain0x/x402 does this for you - it answers 402s automatically.
  4. Your server verifies the X-Payment header and fulfills the original request.

Both sides, real packages

On the pay side, createX402Client({ sdk }) from @blockchain0x/x402 wraps fetch and answers 402s automatically. On the receive side, createX402Plugin (Fastify) and createX402Middleware (Express) gate your own endpoints. Or skip x402 and use the REST API directly - the two compose, so you can add x402 when you are ready.

FREQUENTLY ASKED

Five API questions developers ask first.

Can I use the API without the SDK?

Yes. The SDKs are thin wrappers around standard JSON over HTTPS with bearer-token auth. Anything that can speak HTTP and HMAC-SHA256 can use the API directly. The SDKs exist for ergonomic typing, retries, and idempotency-key generation; they are convenient, not required. We ship clients for Node, Python, Ruby, Go, and JVM; in any other language, call the raw API.

What happens if my webhook endpoint is down when an event fires?

Deliveries retry with backoff and each attempt is logged in the dashboard with its HTTP response code. If you miss an event, you can reconcile from the API - fetch the transaction by id with GET /v1/transactions/{id} - rather than relying on the webhook alone. Verify every delivery with webhooks.verify (or the documented HMAC) before trusting it, and respond 2xx quickly so a slow handler does not look like a failure.

How do I prevent duplicate payments if my agent retries on network errors?

Send an Idempotency-Key header on the POST. payments.create in the SDK auto-mints a stable one for you, so a retried call collapses to a single on-chain submission rather than paying twice. Override it with your own key when you want to dedupe the same logical operation across processes. The key is the safety net for exactly the case where your agent or the network retries mid-flight.

Is there a rate limit?

Yes, and it scales with your plan. Rather than hard-code numbers that drift, read them off the response: every call returns X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset, and a 429 includes Retry-After in seconds. The official SDKs already retry on 429 and 5xx with exponential backoff and honour Retry-After, so most code never has to handle this by hand.

Is this x402-compatible?

Yes. The x402 packages are real: createX402Client({ sdk }) from @blockchain0x/x402 answers 402 challenges on the pay side, and createX402Plugin (Fastify) / createX402Middleware (Express) gate your own endpoints on the receive side. The only scheme today is exact-usdc, and the wire format is an X-Payment: exact-usdc:<base64(json)> request header against a 402 whose body advertises version 1 and an accepts[] list. You can also ignore x402 and just use the REST API directly.

Ship your first payment.

Free to start. A few lines from API key to your first USDC transfer on Base.