Skip to main content
LearnGuidesAdd payments to your AI agent
GUIDE

How to add payments to your AI agent.

10 minutes
SHORT ANSWER

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.

PREREQUISITES

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 to sk_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).
STEP 1 OF 5

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.

TypeScript
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}
Python
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.

STEP 2 OF 5

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.

TypeScript
// 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
Python
# 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 transfer
STEP 3 OF 5

Handle 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.

TypeScript (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 });

  if (result.eventType === "payment.received") {
    // USDC landed - deliver the work, fulfil the order, etc.
    void deliver(result.eventId);
  }
  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 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)
STEP 4 OF 5

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.

Read (curl)
curl https://api.blockchain0x.com/v1/agents/agt_123/spend-permissions \
  -H "Authorization: Bearer $BLOCKCHAIN0X_API_KEY"
Response
{
  "allowance_wei": "5000000",
  "per_tx_wei": "1000000",
  "period_seconds": 86400,
  "revoked_at": null
}
STEP 5 OF 5

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.

COMMON PITFALLS

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.

NEXT STEPS

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).

Full API reference lives at docs.blockchain0x.com. Product surface for the same APIs: Payment API.

Last reviewed: 2026-05-15. Published under CC BY 4.0.

One POST and your agent is getting paid.

Free to start. Test keys included. First payment confirmed in under ten minutes.