How to add payments to your AI agent.
Create an agent with createClient from @blockchain0x/node (or the Python client), send a USDC payment with payments.create, and verify the signed webhook with webhooks.verify. Spend controls are set in the dashboard and read-only over the API. The agent never touches private keys directly. Under ten minutes from sign-up to your first USDC payment on Base, in TypeScript or Python.
Before you start.
- A Blockchain0x account (free signup).
- An API key from the dashboard (use a
sk_test_key for this guide; you will switch tosk_live_later). - Node.js 20+ or Python 3.11+ in your agent runtime.
- An agent built on any framework - LangChain, CrewAI, AutoGen, LlamaIndex, OpenAI Agents SDK, MCP, or plain SDK code. The instructions are framework-agnostic.
- An HTTPS endpoint reachable from the public internet to receive webhooks (ngrok or a deploy preview is fine for development).
Create the agent profile.
The agent profile is the addressable identity behind every payment your agent sends or receives. It carries the wallet address, the public page, the verification badges, and (later) the spend policy. Create one per logical agent.
import { createClient } from "@blockchain0x/node";
const client = createClient({ apiKey: process.env.BLOCKCHAIN0X_API_KEY! }); // sk_test_ / sk_live_
const agent = await client.agents.create({ name: "research-bot" });
console.log(agent.id); // "agt_..."
// Public page: https://wallet.blockchain0x.com/a/{slug}from blockchain0x import Client
client = Client() # reads BLOCKCHAIN0X_API_KEY from the environment
agent = client.agents.create(body={"name": "research-bot"})
print(agent["id"]) # "agt_..."
# Public page: https://wallet.blockchain0x.com/a/{slug}After this call, the agent has a public page at https://wallet.blockchain0x.com/a/<slug> that any counterparty (human or agent) can hover for verification info. See the agent payment identity glossary entry for what that page exposes.
Send a payment.
payments.create sends USDC from the agent wallet. amountWei is base units (USDC has 6 decimals), so 0.01 USDC is the string "10000". The SDK auto-stamps an Idempotency-Key, and the call can return 503 until the chain adapter is wired for your network. To RECEIVE instead, settle an invoice you created in the dashboard with paymentRequests.settle - see the payment API page.
// Send a USDC payment from the agent wallet. amountWei is base units
// (USDC has 6 decimals): "10000" is 0.01 USDC. payments.create auto-stamps an
// Idempotency-Key and can return 503 until the chain adapter is wired.
const tx = await client.payments.create({
agentId: agent.id,
to: "0xRecipient",
amountWei: "10000",
});
console.log(tx); // the submitted transfer# amountWei is USDC base units (6 decimals): "10000" is 0.01 USDC.
tx = client.payments.create(body={
"agentId": agent["id"],
"to": "0xRecipient",
"amountWei": "10000",
})
print(tx) # the submitted transferHandle the webhook.
Webhooks are how you find out a payment settled. In Node, webhooks.verify from @blockchain0x/node does the HMAC check and returns a discriminated union; in other languages, compute the same HMAC over the raw body. Branch on the event type (payment.received for inbound), respond 2xx quickly, and queue anything heavier behind the 2xx so the delivery does not time out.
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 });
if (result.eventType === "payment.received") {
// USDC landed - deliver the work, fulfil the order, etc.
void deliver(result.eventId);
}
res.status(200).send("ok");
});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 webhook():
raw = request.get_data() # RAW bytes - do not parse first
sig = request.headers.get("X-Blockchain0x-Signature", "")
ts = request.headers.get("X-Blockchain0x-Timestamp", "")
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)
if request.headers.get("X-Blockchain0x-Event-Type") == "payment.received":
deliver(request.get_json()) # USDC landed
return ("ok", 200)Set spend controls in the dashboard.
If your agent only receives, you can skip this. If it also pays, set a spend permission - an allowance per period and a per-transaction cap - in the dashboard. It is enforced by the backend on every payment, so it survives prompt injection in a way agent-side rules never can. There is no API or SDK call that mutates a permission (the agent's own key cannot widen its limit); the API is read-only, so your code can fetch the current values to display or plan around.
curl https://api.blockchain0x.com/v1/agents/agt_123/spend-permissions \
-H "Authorization: Bearer $BLOCKCHAIN0X_API_KEY"{
"allowance_wei": "5000000",
"per_tx_wei": "1000000",
"period_seconds": 86400,
"revoked_at": null
}Test the whole flow on Base Sepolia.
Before flipping to sk_live_ keys, run the full path end-to-end with sk_test_. A test key keeps everything on Base Sepolia, where you fund the wallet from the public faucet and the response shapes match live. The key prefix picks the network, so a test key cannot move mainnet funds.
Exercise three scenarios: a happy-path payment that fires payment.received, a missed delivery (point the webhook at a dead URL, then reconcile by fetching the transaction with transactions.get), and a webhook retry (return a 500 the first time, 200 the second, and confirm your handler is idempotent). When all three pass on test, swap the key and ship.
Five mistakes that cost teams a week.
Skipping webhook signature verification
If you accept any POST to /webhooks/payment as authoritative, an attacker can mint fake payment events and trick your agent into delivering work for free. Always HMAC-verify with the webhook secret, using a constant-time comparison. The first compromise is almost always the missing verification.
Assuming a separate confirmation event
The shipped events are payment.received, payment.sent, wallet.deployed, and webhook.test - there is no separate confirmation event. payment.received fires when the transfer is in a block. For most work, that is your signal to deliver. For something expensive or irreversible, poll the transaction with transactions.get and apply your own confirmation threshold before acting; do not wait for an event that does not exist.
No idempotency on webhook handlers
Webhooks retry on non-2xx responses, and the same event will arrive multiple times under load. Your handler must be idempotent: keep a small table of event IDs you have already processed and skip duplicates. Otherwise a transient blip will deliver the same work twice and you will spend hours debugging double-fulfilments.
Mixing test and live API keys
Test keys (sk_test_) hit the sandbox and use Base Sepolia; live keys (sk_live_) hit production and use Base mainnet. Mixing them up in environment configs is the cause of most 'works in dev, fails in prod' tickets. Hard-fail at startup if your runtime environment and key prefix do not match.
Treating a missing webhook as a failed payment
There is no failure event, and a webhook can be missed (your endpoint was down, a delivery was dropped). Do not leave the agent stuck in a 'waiting for funds' loop. Reconcile: fetch the transaction with transactions.get to learn the real state, and put a timeout on any waiting flow so an abandoned payment releases held resources instead of hanging forever.
Once you have your first payment.
With basic payments working, the follow-ups that pay off most are spend controls (so the agent cannot run away with the budget), webhook robustness (so payments do not silently drop under load), and identity verification (so counterparties trust the agent's public page).
Set up agent spend controls that survive prompt injection
The webhook patterns developers ask about most
Earn the GitHub and domain verification badges
Full API reference lives at docs.blockchain0x.com. Product surface for the same APIs: Payment API.