LangGraph payment integration.
No LangGraph package needed. A plain node calls the real blockchain0x client, interrupt() suspends until the payment webhook lands, and the graph resumes - durable across runs.
There is no LangGraph package, and you do not need one. A plain node in your calls the real client, an suspends the graph until the payment webhook lands, and the handler resumes it with LangGraph's own + . Payments settle on Base.
Durable execution makes the resume-after-payment pattern natural.
The hard part of agent-driven payments is the async gap: you request payment, the buyer pays minutes later (or never), and you have to resume work without losing the state of where you were. Most agent frameworks rely on a job queue + manual state-store to bridge this gap. LangGraph already has the primitive built in: interrupt() suspends a graph at a node and a checkpointer persists the state to disk. Resuming is one call.
The recipe leans into this with no special machinery. A plain pay node calls blockchain0x.payments.create. An await_settlement node calls interrupt() to suspend, and the checkpointer persists the state. The webhook handler verifies the signature, sets settled on the matching thread with graph.updateState, and calls graph.invoke to continue from the interrupt. The graph picks up exactly where it left off, including any LLM context from earlier nodes - all using LangGraph's own APIs.
Install LangGraph and the core SDK. No extra package.
Node 18+ and @langchain/langgraph 0.2+ plus the real @blockchain0x/node client. There is no LangGraph adapter to add in either language - Python LangGraph users install langgraph and the blockchain0x Python client and write the same recipe, with interrupt() and Command(resume=...) on the Python side.
npm install @langchain/langgraph @blockchain0x/node
export BLOCKCHAIN0X_API_KEY=sk_test_... # sk_test_ = Base Sepolia, sk_live_ = Base mainnet export BLOCKCHAIN0X_WEBHOOK_SECRET=... # for the webhook handler
BLOCKCHAIN0X_API_KEY is a sk_test_ testnet or sk_live_ mainnet key from your dashboard; the client reads it from the environment. BLOCKCHAIN0X_WEBHOOK_SECRET (returned once when you create or rotate a webhook) is needed in the process that handles webhooks. If your graph runs in one process and the handler in another, both need the same checkpointer (e.g. shared Postgres) so the handler can find the suspended thread.
A three-node graph: pay, suspend, deliver.
Below is a complete LangGraph workflow with a typed State, a pay node that calls the real blockchain0x.payments.create, an interrupt-based await_settlement node, and a deliver node that runs the dependent work once the chain confirms the payment moved.
import { StateGraph, START, END, MemorySaver, interrupt, Annotation } from "@langchain/langgraph"; import { createClient } from "@blockchain0x/node"; const blockchain0x = createClient({ apiKey: process.env.BLOCKCHAIN0X_API_KEY! }); const State = Annotation.Root({ agentId: Annotation<string>(), to: Annotation<string>(), amountWei: Annotation<string>(), // USDC base units: "10000" = 0.01 USDC settled: Annotation<boolean>(), }); // A plain node that calls the real SDK. No dedicated package needed. async function pay(state: typeof State.State) { await blockchain0x.payments.create({ agentId: state.agentId, to: state.to, amountWei: state.amountWei, }); return {}; } // Suspend the graph until the payment.sent webhook resumes this thread. function awaitSettlement() { interrupt("awaiting on-chain settlement"); return {}; } async function deliver() { // Funds have moved - do the work that depended on the payment. return {}; } const graph = new StateGraph(State) .addNode("pay", pay) .addNode("await_settlement", awaitSettlement) .addNode("deliver", deliver) .addEdge(START, "pay") .addEdge("pay", "await_settlement") .addConditionalEdges("await_settlement", (s) => (s.settled ? "deliver" : END)) .addEdge("deliver", END) .compile({ checkpointer: new MemorySaver() });
When the graph runs, the pay node submits the USDC transfer through the SDK. The await_settlement node calls interrupt(), which suspends the graph and persists state via the checkpointer. The conditional edge waits to be told settled=true before routing to deliver; if settlement never lands, a timeout sweep routes it to END. amountWei is base units, so 0.01 USDC is "10000".
Verify the webhook, then resume the thread.
The handler verifies the signature with webhooks.verify from the Node SDK, looks up the suspended LangGraph thread from your own mapping, sets settled with graph.updateState, and calls graph.invoke(null, config) to continue from the interrupt. No special helper - those are LangGraph's own APIs.
import express from "express"; import { webhooks } from "@blockchain0x/node"; import { graph } from "./graph"; 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", async (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.sent") { // You map the event to the suspended graph thread (your own store). const config = { configurable: { thread_id: threadFor(result.eventId) } }; await graph.updateState(config, { settled: true }); await graph.invoke(null, config); // resume from the interrupt } res.status(200).send("ok"); });
webhooks.verify does HMAC-SHA256 in constant time over the raw body and returns a discriminated union - branch on result.ok, no try/catch. Map result.eventId to the thread you suspended, then graph.updateState + graph.invoke(null, config) resumes from the persisted checkpoint and the graph runs deliver. The shipped events are payment.received, payment.sent, wallet.deployed, and webhook.test.
The client you are wrapping is open. Read it.
There is no LangGraph starter package to clone - the recipe above is the integration. The blockchain0x SDKs are open source on GitHub; this recipe wraps the Python SDK (blockchain0x-python), with the full method surface in the docs. Read it for a reference for the node bodies and the webhook verify.
github.com/tosh-labs/blockchain0x-pythonThe full SDK method surface and the webhook signature scheme are documented at the docs. Start on a sk_test_ key against Base Sepolia, then switch to sk_live_ once the graph resumes the way you expect.
Five LangGraph-specific traps to avoid.
LangGraph's durable-execution model is powerful but has its own footguns around checkpoints, interrupts, and state updates.
There is no LangGraph package - a plain node calls the SDK
Blockchain0x ships adapters for LangChain and CrewAI plus the MCP server; there is no dedicated LangGraph package, in npm or pip. The recipe above is the path: an ordinary graph node calls the real @blockchain0x/node client, and you resume with LangGraph's own graph.updateState + graph.invoke. There is no special payment-node class and no resume helper to import - those never existed.
Missing checkpointer
LangGraph's interrupt() only works if the graph has a checkpointer. Without one, the state evaporates when your process exits and the webhook handler has no thread to resume. Use MemorySaver for development and SqliteSaver or PostgresSaver in production. If your graph and webhook handler run in different processes, they must share the same checkpointer (e.g. one Postgres) so the handler can find the suspended thread.
Mapping the webhook to the right thread is your job
The webhook tells you a payment settled; it does not know your LangGraph thread_id. Keep your own mapping - when you start a graph, store thread_id keyed by the agent, the recipient, or an idempotency key you set, then look it up in the handler. The threadFor() call above is that lookup. Get this wrong and graph.invoke resumes the wrong thread or none at all.
Handle the no-settlement path, or suspend forever
The conditional edge from await_settlement routes to deliver only when settled is true. If a payment never lands, the thread stays suspended indefinitely. Add a timeout: a scheduled sweep that resumes stale threads with settled=false so the edge routes to END, or a payment.sent-versus-deadline check. A suspended thread costs nothing, but it also never finishes on its own.
Amounts are USDC base units, as strings
payments.create takes amountWei: a string of USDC base units (6 decimals), so 0.01 USDC is "10000" and 5 USDC is "5000000". It also does not retry by default and can answer 503 until the chain adapter is wired for your network. Handle that in the pay node - route to END or a retry node on error rather than letting the graph wedge.