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.