What you will track
A complete, trustworthy record of every payment an AI agent makes and receives, settled in USDC on Base. You will capture transactions as they happen, confirm any one of them on demand, and reconcile your record against both the API and the chain. By the end you have a ledger you can answer questions from: what did this agent spend, what did it earn, and did a specific payment really happen.
This is the tracking and reconciliation guide. The webhook plumbing it builds on is in how-to-add-webhook-to-ai-agent, and the receive flow in how-to-receive-payments-as-ai-agent. The payment API product page is the broader reference.
Two sources of truth
Tracking uses two complementary sources, and knowing which is which keeps your ledger honest.
The first is the webhook event stream. payment.sent and payment.received arrive the instant a payment settles, and they are your real-time feed. You do not poll for transactions; you record them as the events land. The second is client.transactions.get(id), the canonical record for a single transaction, which you call when you need to confirm status or amount rather than react. There is no list-all endpoint to page through, which is the part people expect and do not find: you build the running ledger yourself from the stream, and use the lookup to verify entries. Behind both sits a third, neutral source, the chain itself, which you reconcile against when it matters.
Prerequisites
- A Blockchain0x account with an agent and a verified webhook (see how-to-add-webhook-to-ai-agent).
- A
sk_test_API key and a store for your ledger (any database). - Node 18 or newer. The SDK targets
>=18.
Build a ledger from the event stream
Record each verified payment event into your own store, keyed by transaction id, separating outbound from inbound by event type.
import { webhooks } from "@blockchain0x/node";
// in a verified, raw-body webhook route:
const result = webhooks.verify({ headers: req.headers, rawBody: req.body, secret: process.env.B0X_WEBHOOK_SECRET! });
if (!result.ok) return res.status(400).json({ code: result.code });
if (result.eventType === "payment.sent" || result.eventType === "payment.received") {
const payload = JSON.parse(req.body.toString("utf8"));
await ledger.upsert({
id: payload.transactionId, // dedupe key
direction: result.eventType === "payment.sent" ? "out" : "in",
agentId: payload.agentId,
deliveryId: result.deliveryId,
});
}
return res.status(200).end();upsert keyed on the transaction id is what makes this safe against the at-least-once delivery: a repeated event updates the same row instead of adding a second. That single discipline is the difference between a ledger you can trust and one that drifts.
Confirm a transaction by id
When you need the authoritative state of a transaction, for a support question, a refund decision, a nightly check, look it up:
import { createClient } from "@blockchain0x/node";
const client = createClient({ apiKey: process.env.B0X_API_KEY! });
const tx = await client.transactions.get("txn_...");
// the canonical record: status, amount, identifiersTreat the webhook as the trigger and transactions.get as the source of truth. A sound tracker reacts to the event in real time and confirms against the record on a schedule, so a missed or delayed webhook never leaves your ledger silently wrong. Store the transaction id from every event so this lookup is a direct fetch rather than a search.
Reconcile against the chain
Because settlement is on chain, you have a neutral third source your own logs cannot fake: the Base block explorer. Every payment has a transaction hash, and pasting it into an explorer shows the transfer, amount, and addresses independently of any API.
Use this for the cases that matter. During development, the explorer confirms a test payment really moved the USDC you expected. In production, it settles a dispute: if your ledger, the API record, and the chain ever disagree, the chain is the arbiter. A sound reconciliation routine checks that all three agree for a sample of transactions periodically, and flags any mismatch, because a gap between them is exactly the kind of problem you want to find on your own schedule rather than during an incident.
What to record per transaction
A ledger is only as useful as the fields you keep, so record enough to answer questions later without going back to the source. At minimum, store the transaction id (your dedupe key), the direction (out or in), the agent id, the amount, the status, and a timestamp. Add the webhook delivery id so you can trace a row back to the exact event, and the on-chain transaction hash so reconciliation against the explorer is a direct lookup.
Resist storing only what you need today. The cheap insurance is recording the identifiers (transaction id, delivery id, chain hash) on every row, because those are what let you confirm, reconcile, and investigate any entry months later. A ledger that kept only amounts and dates can tell you what happened in aggregate but cannot prove any single payment, which is exactly what a dispute or an audit asks for. Keep the ids; they cost almost nothing and are the difference between a report and an evidence trail.
Attribute by agent
For a single agent this is trivial, but a fleet needs per-agent attribution, and the data supports it cleanly. Each agent's wallet has its own event stream, so record the agentId on every ledger entry and you can answer per-agent questions directly: which agent spent the most, which earned, which one's spend rate jumped.
That attribution is the payoff of one wallet per agent. Spend and revenue roll up per agent for cost accounting, and a per-agent view makes anomalies obvious, a researcher agent suddenly spending like a trading agent stands out immediately. Aggregate across agents for a workspace total, drill into one for detail, all from the same id-keyed ledger you built from the stream.
Turn the ledger into reports
Once the ledger is solid, the reports come almost for free, because you already have the fields. Group by agent and period for a spend-and-revenue breakdown; sum by direction for net flow; filter by status to find pending or failed payments that need attention. Because every entry carries a timestamp and an amount, time-series views (spend per day, revenue per week) are a query, not a new pipeline.
The same ledger feeds three audiences from one source. Finance wants totals per period for accounting. Engineering wants spend rate and failure counts to catch problems. A customer or counterparty asking "did my payment go through" wants a single transaction looked up by id and, if needed, the chain hash to prove it. Build the ledger once, with the identifiers intact, and you serve all three without standing up separate systems. Export it to your warehouse on a schedule if you want it alongside the rest of your business data, keyed by the same transaction ids so it joins cleanly.
Common pitfalls
Three traps tracking agent transactions.
Expecting a list endpoint. You build the ledger from the payment.sent/payment.received stream and confirm with transactions.get. Capture events as they arrive rather than waiting to page a list.
Double-counting redelivered events. Webhook delivery is at-least-once. Key the ledger on the transaction id and upsert, so a repeat updates rather than duplicates.
Trusting one source alone. Your logs, the API record, and the chain should agree. Reconcile periodically so a discrepancy surfaces early.
What to ship today
Capture payment.sent and payment.received into an id-keyed ledger from a verified webhook, confirm entries with transactions.get, and reconcile a sample against a Base explorer on a schedule. Record agentId on every entry so a fleet rolls up cleanly. For the webhook setup this builds on, see how-to-add-webhook-to-ai-agent. Pricing is on the pricing page.