What you will build
A LangChain agent that pays for the services it calls, settled in USDC on Base. The agent's planner never sees the payment. It calls a normal tool, the tool fetches a paid API, and when that API answers 402 Payment Required the x402 client settles the bill and retries. You wire this once. After that, every paid call the agent makes flows through its Blockchain0x wallet, bounded by a spend limit you set in the dashboard.
This is the LangChain-specific version of the architecture on the LangChain integration page. If you already have an agent and just want it to pay, you are in the right place. The core is about fifteen lines.
There is no Blockchain0x LangChain package. You do not need one. The honest pattern is to wrap the real x402 fetch client in LangChain's own tool type, and that is exactly what the code below does.
Prerequisites
Have these ready before you start:
- A working LangChain agent in TypeScript (any executor:
AgentExecutor,createToolCallingAgent, LangGraph). - A Blockchain0x account with one agent created. The add-payments-to-agent guide walks through that in five minutes.
- An API key. Use a
sk_test_key for this walkthrough and switch tosk_live_when you ship. - Node 18 or newer. The SDK targets
>=18.
Set the key where your runtime reads env:
export B0X_API_KEY=sk_test_... # sk_test_ -> Base Sepolia, sk_live_ -> Base mainnetInstall the packages
Two real packages, plus LangChain core if you do not already have it.
npm install @blockchain0x/node @blockchain0x/x402 @langchain/core@blockchain0x/node is the authenticated client. @blockchain0x/x402 is the payment layer: its ./client export gives you a drop-in fetch that answers 402s. That is the whole dependency list. If you went looking for a dedicated LangChain adapter package, there is not one, and you will not miss it.
Wrap a paid API in a LangChain tool
Build one client per process, then wrap the x402 fetch in a DynamicStructuredTool. The agent treats it like any other tool.
import { DynamicStructuredTool } from "@langchain/core/tools";
import { createClient } from "@blockchain0x/node";
import { createX402Client } from "@blockchain0x/x402/client";
import { z } from "zod";
// The sk_test_ prefix pins this client to Base Sepolia.
const sdk = createClient({ apiKey: process.env.B0X_API_KEY! });
// fetchWithPay behaves like fetch. On an HTTP 402 it parses the payment
// requirement, builds the `X-Payment: exact-usdc:<base64>` header, settles
// in USDC, and re-issues the request. You just await the final Response.
const fetchWithPay = createX402Client({ sdk });
const realtimeQuote = new DynamicStructuredTool({
name: "get_quote_realtime",
description: "Fetch a real-time quote for a stock ticker.",
schema: z.object({ ticker: z.string() }),
func: async ({ ticker }) => {
const res = await fetchWithPay(
`https://quotes.example.com/v1/quote?ticker=${encodeURIComponent(ticker)}`,
);
if (!res.ok) {
// 402 is handled for you; a non-2xx here means the upstream itself
// failed or the payment was rejected. Surface it, do not throw blindly.
return `Quote lookup failed with status ${res.status}.`;
}
return await res.text();
},
});That is the entire integration. The tool's schema, name, and description are unchanged from an unpaid tool, so the planner reasons about it exactly as before. The only new line is the fetchWithPay wrapper.
Run the agent
The payment-aware tool plugs into your existing agent with no other changes.
import { createToolCallingAgent, AgentExecutor } from "langchain/agents";
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({ model: "gpt-4o" });
const agent = await createToolCallingAgent({ llm, tools: [realtimeQuote], prompt: yourPrompt });
const executor = new AgentExecutor({ agent, tools: [realtimeQuote] });
await executor.invoke({ input: "What is the live quote on TSLA?" });The first time the agent calls get_quote_realtime, the upstream returns 402, the wallet settles the price in USDC, and the retried request returns the quote. The planner sees a normal tool result. It does not know money changed hands, and it does not need to.
Confirm payments with a webhook
Webhooks matter when the paid call starts work that finishes later. For a synchronous lookup like the one above, skip this section. For anything async, verify every delivery with webhooks.verify from the SDK. Verify against the raw bytes, never the parsed JSON, because the HMAC is computed over exactly what arrived on the wire.
import express from "express";
import { webhooks } from "@blockchain0x/node";
const app = express();
app.use(express.raw({ type: "application/json" }));
app.post("/webhooks/payment", (req, res) => {
const result = webhooks.verify({
headers: req.headers,
rawBody: req.body, // Buffer of raw bytes
secret: process.env.B0X_WEBHOOK_SECRET!, // returned once when you create the webhook
});
if (!result.ok) {
return res.status(400).json({ code: result.code });
}
const payload = JSON.parse(req.body.toString("utf8"));
if (result.eventType === "payment.sent") {
deliverAsyncResult(payload);
}
return res.status(200).end();
});The verifier compares the HMAC in constant time and rejects any delivery whose timestamp is more than five minutes off, which kills replay attempts. The events you care about for an agent that spends are payment.sent (the agent paid) and payment.received (someone paid the agent).
Set spending limits
This is the part people get wrong, so read it twice. You do not set spend limits from the SDK, and you never put them in the prompt. They live in the dashboard, enforced by the wallet API before settlement, which is the one place prompt injection cannot reach them.
You can read back what is enforced to confirm it:
const agentId = "agt_...";
const res = await fetch(
`https://api.blockchain0x.com/v1/agents/${agentId}/spend-permissions`,
{ headers: { Authorization: `Bearer ${process.env.B0X_API_KEY!}` } },
);
const permissions = await res.json();
// Each permission exposes allowance_wei, per_tx_wei, and period_seconds
// (86400 daily, 604800 weekly, or 2592000 monthly), plus start_at / end_at.The wallet checks these on every payment intent. A call that would break per_tx_wei or blow through the period allowance_wei is refused before any USDC moves. Set conservative numbers in the dashboard first, watch real usage for a week, then loosen. The agent-spend-controls guide covers the dashboard side.
Test on Base Sepolia
Run the whole flow on testnet before you touch a sk_live_ key. A sk_test_ key settles on Base Sepolia (eip155:84532) with the same request and response shapes as mainnet.
Fund the agent first. Copy the agent's wallet address from the dashboard (or its public page at https://wallet.blockchain0x.com/a/{slug}) and send it test USDC from a public Base Sepolia USDC faucet. Then run the agent and watch the paid tool settle.
One honest caveat: outbound settlement is in active rollout. On some networks the settle path can return a 503 until the chain adapter is live there, so write your tool to handle a non-2xx response cleanly rather than assume every paid call succeeds. The test-agent-payments guide documents the sandbox flow in more depth. Production costs are on the pricing page: the Free tier covers this walkthrough, and paid tiers drop the transaction fee.
Common pitfalls
Four mistakes that cost teams an afternoon each in their first week.
Verifying the parsed body instead of the raw bytes. The webhook HMAC is computed over the exact bytes on the wire. If a framework middleware parses the JSON before you read it, you re-serialize a slightly different string and the signature never matches. Mount express.raw on the webhook route and pass the Buffer straight into webhooks.verify.
Mixing test and live keys. A LangChain agent that boots with sk_test_ but points at production URLs is the most common config bug in week one. The server stamps the network from the key prefix and rejects a mismatched caller with apikey.network_mismatch, but you want to catch it earlier. Fail the boot:
const apiKey = process.env.B0X_API_KEY!;
if (process.env.NODE_ENV === "production" && apiKey.startsWith("sk_test_")) {
throw new Error("Test key in production. Aborting boot.");
}
if (process.env.NODE_ENV !== "production" && apiKey.startsWith("sk_live_")) {
throw new Error("Live key outside production. Aborting boot.");
}Putting the budget in the prompt. "You may spend up to five dollars per task" in a system prompt is not a control, it is a suggestion, and a crafted input will talk the model past it. The spend permission in the dashboard is the real boundary. The prompt should describe behavior; the wallet enforces money.
Assuming the agent retries 402 itself. It does not. The x402 client handles the 402 and retry inside fetchWithPay. Your tool sees only the final response. Do not write your own 402 branch on top of it, or you will pay twice for one call.
What to ship today
Take one thing from this guide: adding payments to a LangChain agent is a single fetchWithPay wrapper around a tool's fetch. Everything else, the webhook and the spend limits, is hardening you layer in as the agent earns trust.
Ship the wrapper on Base Sepolia today, watch one real paid call settle, then set a dashboard spend limit before you go near a sk_live_ key. If your agent will also sell a service, the receive side is the monetize-mcp-server guide. If it only consumes paid APIs, the spend limit is the highest-value thing you can do this week.