What adding crypto payments means
You want your API to accept crypto, so a caller pays in a digital currency rather than a card. For an API, accepting crypto really means two things: receiving the funds, and knowing which request a payment was for so you can gate access. The first is easy; the second is where most naive approaches fall down, and it is the part that decides whether your crypto payments actually work as a paywall rather than just a tip jar.
The clean answer for an API, especially one called by programs and agents, is USDC on Base charged per request through x402. This page explains why that is the right approach and how it compares to the obvious-but-weaker alternative, then wires it. For the focused x402 mechanics see how-to-add-x402-to-my-api, and for the stablecoin angle see how-to-accept-stablecoin-payments-as-agent. The payment API product page is the broader reference.
The naive way and why it falls short
The first idea most people have is to post a wallet address and ask callers to send crypto to it. It receives money, so it feels like accepting crypto payments, but it breaks as an API paywall for one reason: a bare transfer to an address carries no link to a specific API request.
When USDC lands at your address, you know someone paid, but not which call it was for, not whether it covered the price of the route they want, and not how to let exactly that request through. You end up building a reconciliation layer to match payments to requests, and racing to gate access after the fact. It also pushes volatility and chain choice onto the caller, and gives them no standard way to know the price before paying. Posting an address is fine for tips or donations. As the payment layer for an API that gates access per call, it is the wrong tool, because the one thing an API paywall needs, tying a payment to a request, is the one thing it does not give you.
Prerequisites
- A Blockchain0x account with an agent created, so you have a wallet and a
payToAddress. - A
sk_test_key and apaymentRequestIdfor each priced route. - An existing Fastify or Express API on Node 18+. The SDK targets
>=18.
export B0X_API_KEY=sk_test_...
export B0X_PAYTO_ADDRESS=0x...The modern way: x402 per call
x402 fixes exactly the gap the address approach leaves. The API answers an unpaid request with HTTP 402 and a quoted price; the caller pays in USDC and retries the same request with proof; the API verifies and serves. Because the payment rides on the request, it is tied to that request by construction. You know what was paid for, you know it covered the price, and you let through exactly that call. No reconciliation layer, no after-the-fact matching.
It settles in USDC on Base, so you price in dollars without token volatility, and the caller sees the price in the 402 before paying. For an API whose callers are programs, this is the difference between accepting crypto and actually running a crypto paywall.
Wire it
Mount the adapter on the routes you want paid. Fastify:
import { createClient } from "@blockchain0x/node";
import { createX402Plugin } from "@blockchain0x/x402/server/fastify";
const sdk = createClient({ apiKey: process.env.B0X_API_KEY! });
await app.register(createX402Plugin, {
sdk,
defaultNetwork: "testnet",
pricing: {
"POST /v1/translate": { amountUsdc: "0.02", payToAddress: process.env.B0X_PAYTO_ADDRESS!, paymentRequestId: "pr_translate" },
},
});
app.post("/v1/translate", existingTranslateHandler); // runs only once paidExpress is the same with createX402Middleware({ sdk, defaultNetwork, pricing }) passed to app.use. Your handler is unchanged; the adapter gates its route, returns the 402 to unpaid callers, and verifies the X-Payment header through the SDK before your handler runs.
What you get that an address does not
It is worth naming the concrete wins over posting an address, because they are the reason to do it this way.
You get payment tied to request: every paid call is verifiably for that call at that price, so gating is automatic, not reconciled. You get a quoted price: the caller learns the cost from the 402 instead of guessing, which is what lets an agent decide whether to pay. You get dollar pricing: USDC means your 0.02 is two cents regardless of market moves. You get no key handling: you configure a payToAddress and the adapter verifies through the SDK, rather than running wallet infrastructure. And you get a standard callers already speak: any x402 runtime pays your API with no custom integration. Posting an address gives you none of these; it gives you a pile of incoming transfers you have to make sense of.
Crypto payments versus a card
Adding crypto does not mean ripping out card payments, and it is worth being clear about when each one wins so you add crypto for the right reasons.
A card is the better rail when your payer is a human buying occasionally, when amounts are large, or when you need consumer protections like chargebacks. The card networks are built for that, and crypto would only add friction. Crypto, specifically USDC on Base via x402, wins when your payer is a program making many small payments with no human to onboard. A card cannot economically authorize a half-cent charge, and an agent cannot fill in a checkout form, so for machine traffic crypto is not a preference, it is the only rail that fits.
The practical answer for many APIs is both, on different routes: keep card-based access for your human customers and add x402 to the endpoints that agents call. They do not interfere, because each lives on its own routes, and you are extending the API to a new kind of payer rather than migrating your existing one. Add crypto where the traffic is machine-driven, and leave the human path on whatever already serves it well.
Test on Base Sepolia
Run it on testnet first. With defaultNetwork: "testnet" and a sk_test_ key, call a priced route with no X-Payment header and confirm the 402 quotes the right amount; pay it from a test wallet and confirm your handler runs; confirm unpriced routes still return normally. When the paid and free paths both behave, set defaultNetwork to mainnet and swap to a sk_live_ key for Base.
Common pitfalls
Three traps.
Treating an address as a paywall. Receiving at an address is not gating access. Use x402 so payment is tied to the request; reserve bare addresses for tips and donations.
Pricing in a volatile token. Quote in USDC so a two-cent route stays two cents. Pricing API access in a volatile asset makes your prices move for no reason.
Flipping a live route to 402 without notice. Moving an existing free endpoint behind the paywall breaks non-x402 clients. Roll it out as a breaking change on a new path with deprecation.
What you get today
Mount the adapter on one route, confirm an unpaid call gets the 402 and a paid call runs, and you are accepting crypto payments the way an API should, tied to requests, priced in dollars, with no key handling. For the focused x402 mechanics see how-to-add-x402-to-my-api. Pricing is on the pricing page.