Skip to main content
LANGGRAPH INTEGRATION

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.

SHORT ANSWER

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.

WHY LANGGRAPH FITS THIS PATTERN

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.

INSTALLATION

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.

INSTALL
npm install @langchain/langgraph @blockchain0x/node
ENVIRONMENT VARIABLES
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.

FULL GRAPH EXAMPLE

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.

GRAPH.TS
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".

WEBHOOK HANDLING

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.

WEBHOOK.TS
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.

SOURCE AND DOCS

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-python

The 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.

COMMON PITFALLS

Five LangGraph-specific traps to avoid.

LangGraph's durable-execution model is powerful but has its own footguns around checkpoints, interrupts, and state updates.

PITFALL 1

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.

PITFALL 2

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.

PITFALL 3

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.

PITFALL 4

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.

PITFALL 5

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.

FREQUENTLY ASKED

Three LangGraph-specific questions.

Is there a LangGraph package, in npm or pip?

No. LangGraph already gives you everything: a node is just a function, interrupt() suspends, a checkpointer persists, and graph.updateState + graph.invoke resume. So the honest path is a plain node that calls the real @blockchain0x/node client (or the Python blockchain0x client), as shown above. The only shipped framework packages are blockchain0x-langchain and blockchain0x-crewai (both Python) plus the @blockchain0x/mcp server.

Can I do this in Python LangGraph as well?

Yes, the same recipe. Python LangGraph has the same primitives - a function node, interrupt(), a checkpointer, and Command(resume=...) or update_state + invoke to continue. Your node calls the Python blockchain0x client (blockchain0x.payments.create(...)), and your webhook handler verifies the signature and resumes the thread. There is no pip package to install beyond langgraph and blockchain0x; the graph definition is nearly identical to the TypeScript one.

Does this work with LangGraph Platform and human-in-the-loop nodes?

Yes. The recipe is plain LangGraph, so it runs the same on LangGraph Platform - point your Blockchain0x webhook at the deployment and resume the thread there. interrupt() also supports several interruption points in one graph: a payment interrupt resumed by the webhook and a human_review interrupt resumed by a UI action can live side by side, as long as each resume updates its own field on the state. The graph routes through them in order.

Add durable payments to your graph.

Pause on payment, resume on webhook, all with LangGraph's own APIs. Free to start.