Skip to main content
This guide covers the LangChain and LangGraph integration shipped in the [langchain] optional extra of payments-py. The module payments_py.x402.langchain provides the decorator, helpers, and exceptions for monetizing LangChain tools with the x402 protocol. For the conceptual walk-through (two integration approaches, the discovery-first flow, dynamic credits patterns), see the LangChain integration guide. For a runnable end-to-end demo, see the langchain-paid-agent-py tutorial.
Looking to gate a LangSmith Deployment entry point (/threads/{id}/runs/wait etc.) rather than individual tools? See LangSmith Deployment Middleware. The two integrations are complementary: the decorator covered here protects tools the agent calls; the LangSmith Deployment middleware protects the agent’s HTTP entry point.

Installation

The [langchain] extra installs langchain-core. langgraph and langchain-openai are optional — needed only if you build a LangGraph agent.

Exports

All four are exported from payments_py.x402.langchain:

requires_payment

Decorator that protects a LangChain @tool with x402 payment verification and settlement. Pulls the access token from RunnableConfig.configurable["payment_token"], verifies it, runs the tool body, then settles credits.

Signature

Decorator order

@tool outside, @requires_payment inside:
The protected function must accept a config: RunnableConfig parameter — that is how the decorator reads the payment token at call time.

Dynamic credits

credits accepts three forms:
  • Static intcredits=1 (fixed cost per call).
  • Lambdacredits=lambda ctx: max(1, len(ctx["result"]) // 100).
  • Named functioncredits=my_fn where my_fn(ctx) -> int.
When callable, ctx is {"args": <tool kwargs>, "result": <tool return>}. Dynamic credits resolve after execution so the result is available.

Credits semantics

The credits argument is sent to the facilitator as max_amount. The actual amount redeemed depends on the plan’s server-side credit configuration:
  • Fixed plans (plan.credits.minAmount == plan.credits.maxAmount) always burn plan.credits.maxAmount. The decorator’s credits=N is then effectively a no-op (per nevermined-io/nvm-monorepo#1568).
  • Range plans clamp the supplied value into [plan.credits.minAmount, plan.credits.maxAmount].
If you want predictable per-call cost, configure the plan as fixed; the decorator value is then a client-side declaration.

PaymentRequiredError

Raised by @requires_payment when the token is missing from config["configurable"]["payment_token"], or when the facilitator rejects the token (expired, invalid signature, insufficient balance, etc.). Carries the full X402PaymentRequired payload so callers can run the x402 discovery flow.

Attributes

For the discovery → acquire → retry flow, see the LangChain integration guide.

last_settlement

Returns the most recent SettleResponse produced by @requires_payment in this process. Use this after invoking a LangGraph runnable to recover the settlement receipt — credits_redeemed, remaining_balance, transaction, network, payer — without threading it back through RunnableConfig.configurable (which LangGraph copies per node, so the SDK’s in-place write is not visible to the outer caller).

Signature

Returns None if no settlement has happened yet in this process, or if the most recent invocation raised before reaching the settle phase.

Example

last_settlement() reads from a module-level slot. In multi-tenant processes (e.g. a server handling concurrent settlements), the value reflects whichever invocation settled most recently — there is no per-call isolation. For multi-tenant scenarios, surface settlement via a callback or observability layer instead.

create_paid_react_agent

Thin wrapper over langgraph.prebuilt.create_react_agent that constructs the underlying ToolNode with handle_tool_errors=False. That single change is what lets PaymentRequiredError propagate all the way back to agent.invoke()’s caller with its X402PaymentRequired payload intact — the default ToolNode behaviour stringifies the exception into a ToolMessage for the LLM and loses the payload.

Signature

langgraph is imported lazily so the [langchain] extra need not pull it in. Install LangGraph yourself (pip install langgraph) to use this helper.

Example

End-to-end usage

The canonical x402 flow uses all four symbols together — discovery, acquisition, retry, receipt read:
For the full, runnable version see tutorials/langchain-paid-agent-py.

Observability with LangSmith

When the optional [langsmith] extra is installed and a LangSmith run is active in the calling context, @requires_payment automatically emits two child spans nested under the active tool span:
  • nvm:verify — opens around the verify-permissions call, with attributes describing the scheme, plan, payer, and verify duration.
  • nvm:settlement — opens around the settle-permissions call, with attributes describing credits redeemed, remaining balance, transaction hash, network, and settle duration.
The same nvm.* metadata is also attached to the parent tool span so the trace is searchable from either level. The per-call child spans are always authoritative; the parent copy is best-effort and can be overwritten when two protected tools run in the same node — see Known limitations.

Install

Enable

No code changes are needed beyond the existing @requires_payment decorator — the spans are emitted automatically when LangSmith is active. If langsmith is not installed or LANGSMITH_TRACING is unset, span emission is a silent no-op.

Regional endpoint

LangSmith hosts accounts across several regions. The SDK defaults to GCP US (https://api.smith.langchain.com); accounts in any other region must set LANGSMITH_ENDPOINT or the trace POST will fail with 403 Forbidden on /runs/multipart.
The payment flow itself is unaffected by trace-shipping failures — verify, tool execution, and settle proceed normally. Only the LangSmith trace ingestion fails, surfaced as langsmith.utils.LangSmithError warnings.

Span attributes

Sensitive data in traces

The payment_token that the buyer passes via config["configurable"]["payment_token"] is captured by LangChain into the parent tool span’s metadata, and would normally be inherited by any child span — including the nvm:verify and nvm:settlement spans the decorator emits. The full token grants access to the protected tool until it expires, so the decorator proactively strips payment_token from the parent tool span’s metadata before opening any child span. The full credential never reaches a Nevermined span attribute. For correlation across spans the decorator surfaces an abbreviated nvm.payment_token attribute (eyJ4NDAyVmVyc2lv…bsig, first 16 chars + ellipsis + last 4) on both nvm:verify and nvm:settlement. That gives you “which token was this?” without exposing the credential itself. A real x402 access token is a JWT, which is far longer than 20 chars. If a token of 20 characters or fewer is passed — almost always a misconfiguration (a plan id or opaque handle where the JWT was expected) — it is redacted, not exported: nvm.payment_token shows at most the first 4 chars plus a …(short) marker (e.g. eyJ4…(short)), and a runtime warning is logged. For a token of 4 chars or fewer, nothing is revealed at all — it collapses to just …(short). The full short value never reaches a span attribute, so a misrouted secret cannot leak into a durable trace store even when it is shorter than the abbreviation threshold. The active redaction covers the documented LangChain-via-configurable path. If you’re surfacing the token through a different channel (custom callbacks, an explicit add_metadata({"payment_token": ...}), raw inputs to a tool whose signature contains the token), the decorator can’t see those — strip them yourself or set export LANGSMITH_HIDE_INPUTS=true for blanket coverage. Other nvm.* attributes that may be considered sensitive depending on your context:
  • nvm.payer — the payer’s wallet address (public on-chain, but a stable identifier).
  • nvm.tx_hash — the settlement transaction id.
  • nvm.agent_request_id — Nevermined-internal correlation id.
  • nvm.balance.after — the payer’s remaining credit balance after this settlement. Reveals per-payer depletion patterns to anyone with trace read access on the operator’s LangSmith project. Suppress with LANGSMITH_HIDE_OUTPUTS=true or post-filter.
None of these grant access on their own.

Known limitations

Parent metadata is last-writer-wins across tools in one node

@requires_payment attaches its nvm.* metadata to two places: the per-call child spans (nvm:verify / nvm:settlement) and, as a convenience for searchability, the parent LangSmith run tree. The child spans are isolated per call, so they are always correct. The parent copy is not namespaced per tool: the bare nvm.* keys (nvm.tx_hash, nvm.credits_redeemed, nvm.payment_token, …) are written directly onto the parent run’s metadata. When an agent calls two @requires_payment tools within the same LangGraph ToolNode — the common pattern, since a single ReAct step can dispatch multiple tool calls into one node — both decorators target the same parent run tree, and the second add_metadata silently overwrites the first’s nvm.* values. The parent therefore reflects only the last tool that settled in that node; the earlier tool’s parent-level nvm.* is lost. What this means in practice:
  • Per-call billing fidelity lives on the child spans, not the parent. For accurate per-tool accounting (which token, which tx hash, how many credits each call redeemed), filter and aggregate on the nvm:verify / nvm:settlement child spans. Each child carries the values for exactly one call.
  • Treat parent nvm.* as best-effort. It is convenient for “did this trace touch Nevermined at all?” searches, but do not rely on it for last-writer-sensitive fields when multiple paid tools can run in one node.
  • This is a last-writer-wins behaviour, not a correctness bug in settlement — every call still verifies and settles independently and correctly. Only the parent’s denormalized copy of the metadata is affected.
This matches the cross-SDK observability spans v1 contract (the SDK-neutral span spec maintained in nvm-monorepo, which both payments-py and @nevermined-io/payments emit against): child spans are authoritative per tool; parent nvm.* is best-effort / last-writer-wins.

Manual use (non-LangChain paths)

The same context managers are also exported for code that wants to emit Nevermined-flavored spans without going through @requires_payment (e.g. the FastAPI middleware path):
Span emission failures are caught internally — observability is best-effort and will not interfere with the payment flow.

Deep Agents

Deep Agents is LangChain’s agent harness: create_deep_agent() returns a compiled LangGraph graph with planning, a filesystem, and subagent delegation built in. requires_payment needs no changes to work with it. The decorator reads the token from config["configurable"]["payment_token"], and LangGraph copies configurable down into subagent tool calls, so a paid tool keeps working when it sits behind a task() delegation:
That property is what makes the harness usable for monetized capabilities at all: a deep agent’s premise is that the supervisor hands work to subagents, so if payment context did not survive the hop, every paid tool would have to sit on the main agent.
The buyer side is unchanged — the token goes on the run, and the buyer does not need to know the agent’s internal topology:

Two harness behaviours to design around

A deep agent can bill several times per user turn. The supervisor, not you, decides how many subagent calls a request warrants, so one user message may settle credits more than once. Cap it explicitly rather than trusting the model to be frugal — count paid calls per run (key on config["configurable"]["thread_id"] or run_id) and return a plain refusal once the cap is hit. Refund the reservation when a call raises PaymentRequiredError, so a user who authorizes mid-run still gets what they paid for. Two LLM layers can paraphrase the tool’s output. The subagent relays to the supervisor, which relays to the user; neither is guaranteed to pass text through verbatim, and a capable supervisor may even answer a paid question from its own knowledge instead of delegating — silently giving the capability away. Forbid that explicitly in both system prompts, and treat the tool’s return value, not the chat reply, as the source of truth.

Version note

deepagents requires the LangChain v1 stack (langchain>=1.3.18, langchain-core>=1.6.1). If your project pins an older langchain-core, give the deep agent its own virtualenv. Compatibility is pinned by tests/unit/x402/test_deepagents_compat.py, which drives a real deep agent through a scripted fake model (no network, no LLM) and asserts the token reaches a subagent’s tool. They run in CI under the dedicated deepagents_compat job — separate from the main test job, which pins the Python 3.10 floor that deepagents cannot install on. To reproduce locally: