What you will build
An AI agent that gets paid in USDC on Base. Maybe it earns tips, maybe it receives payouts from a system you run, maybe it sells a service and charges per call. All three are the same underlying capability: the agent has a wallet, USDC arrives in it, and you find out when it does. By the end you can both receive money passively and charge for a service actively, with verification you can trust.
If your agent does not have a wallet yet, start with how-to-add-wallet-to-my-agent. The payment API product page is the broader reference.
Two ways an agent gets paid
It is worth separating these up front, because they need different code.
The first is passive receipt. Someone, a person or another agent, sends USDC to your agent's wallet address. You did not gate anything; the money simply arrives, and you want to know when. This is the shape for tips, payouts, treasury transfers, and any case where the decision to pay was made elsewhere.
The second is active charging. Your agent offers a service over an HTTP endpoint, and you want callers to pay before they get a result. Here you put a paywall in front of the endpoint so an unpaid request gets a 402 and a paid one gets the response. This is the shape for an agent that sells research, generation, lookups, or any per-call product.
Most agents start with the first and add the second when they have something worth charging for.
Prerequisites
- A Blockchain0x account with one agent created, so it has a wallet.
- A
sk_test_API key for this walkthrough. - Node 18 or newer. The SDK targets
>=18.
export B0X_API_KEY=sk_test_... # sk_test_ -> Base Sepolia
export B0X_WEBHOOK_SECRET=... # the webhook signing secret, shown once when you create the webhookReceive directly to the wallet
Passive receipt needs almost nothing from you. The agent's wallet has an address on Base, shown in the dashboard and on the agent's public profile at wallet.blockchain0x.com/a/{slug}. Anyone who sends USDC to that address on the right network credits the agent's balance. On a sk_test_ agent that is Base Sepolia; on sk_live_ it is Base mainnet.
The public profile is also where a counterparty checks the agent before paying it. The verification badges on that page, email, GitHub, and domain, are what turn an address into something a careful payer trusts. If your agent will receive money from strangers, earn those badges early; they materially change whether a first-time payer goes through with it.
Watch for incoming payments
You want to act when money arrives, so subscribe to the payment.received webhook and verify every delivery. Verify against the raw bytes, never the parsed JSON, because the HMAC is computed over exactly what arrived.
import express from "express";
import { webhooks } from "@blockchain0x/node";
const app = express();
app.use(express.raw({ type: "application/json" }));
app.post("/webhooks/blockchain0x", (req, res) => {
const result = webhooks.verify({
headers: req.headers,
rawBody: req.body, // raw bytes
secret: process.env.B0X_WEBHOOK_SECRET!,
});
if (!result.ok) return res.status(400).json({ code: result.code });
if (result.eventType === "payment.received") {
const payload = JSON.parse(req.body.toString("utf8"));
creditAgentLedger(payload); // your logic: thank the payer, release work, update a balance
}
return res.status(200).end();
});The verifier rejects deliveries more than five minutes old, which kills replay attempts, so you do not have to dedupe by timestamp yourself. payment.received is the inbound event; payment.sent is the one for payments the agent makes.
Charge callers for a service
When the agent sells something per call, gate its endpoint with the x402 server adapter. The adapter answers a 402 with your price and verifies payment on the retry, before your handler runs.
import Fastify from "fastify";
import { createClient } from "@blockchain0x/node";
import { createX402Plugin } from "@blockchain0x/x402/server/fastify";
const sdk = createClient({ apiKey: process.env.B0X_API_KEY! });
const app = Fastify();
await app.register(createX402Plugin, {
sdk,
defaultNetwork: "testnet",
pricing: {
"POST /agent/answer": {
amountUsdc: "0.02",
payToAddress: process.env.B0X_PAYTO_ADDRESS!, // the agent's wallet
paymentRequestId: process.env.B0X_PRICE_REQUEST_ID!,
},
},
});
app.post("/agent/answer", async (req) => runAgentTask(req.body)); // only runs once paid
await app.listen({ port: 8080 });Express works the same way with createX402Middleware. Pricing is per route, so put each priced service on its own path and leave anything free, like a health check or a capability listing, on an unpriced one.
Verify a payment server-side
The property that makes charging safe is that the adapter never trusts the caller's claim. On the retry it reads the X-Payment header and calls paymentRequests.settle to confirm the on-chain transfer matches the quoted requirement: right amount, right network, right payment request. Only a confirmed payment reaches your handler; a missing, malformed, or mismatched one is rejected with a fresh 402 and a reason you can log (header_missing, requirement_mismatch, settle_rejected, and a few others).
This is the same discipline a card processor's webhook verification gives you, and you get it without writing the verification yourself. Do not be tempted to trust a caller's "I paid" to save a round trip; that shortcut is how paid services leak revenue.
Reconcile what arrived
Webhooks tell you about payments as they happen, but you also want a way to look one up after the fact, for a support question, a refund decision, or a nightly reconciliation job. Read a single payment by its id:
const tx = await client.transactions.get("txn_...");
// tx carries the canonical record for that transaction: status, amount, and identifiersTreat the webhook as the trigger and the transaction record as the source of truth. A dependable receive pipeline does both: it reacts to payment.received in real time for the fast path, and it reconciles against the transaction record on a schedule so a missed or delayed webhook never leaves your ledger wrong over the long run. The two together are what lets you answer "did this specific payment land, and for how much" with confidence weeks later, rather than trusting an event you may or may not have processed. Keep the transaction id from the webhook payload so the lookup is a direct fetch rather than a search.
Common pitfalls
Three traps on the receive side.
Verifying the parsed body instead of the raw bytes. The HMAC is over the exact bytes. If a JSON middleware parses the body before you read it, the signature will not match. Mount the raw-body parser on the webhook route.
Receiving on the wrong network. A sk_test_ agent's wallet is on Base Sepolia. USDC sent on the wrong chain does not arrive. Confirm the network before you share an address with a payer.
No identity on a public-facing agent. An agent receiving money from strangers with a bare address and no verification badges converts poorly. Earn the badges on the public profile before you promote the address.
What to ship today
Subscribe to payment.received, verify it with webhooks.verify, and send your test agent a dollar of test USDC to watch the event land. If the agent sells a service, gate one route with the adapter and confirm an unpaid call gets a 402. Then earn the verification badges before you put the address in front of real payers. Production pricing is on the pricing page.