Skip to main content
A Starlette ASGI middleware (PaymentMiddleware) that gates a LangGraph agent deployed to LangSmith Deployment (formerly LangGraph Platform) with the Nevermined x402 payment flow. Use the build_payment_app factory for one-line wiring via the http.app field in langgraph.json. For tool-time gating inside an agent (@requires_payment decorator), see LangChain Integration. The two integrations are complementary — that page is for protecting tools the agent calls; this page is for protecting the agent’s HTTP entry point.

Installation

The [langsmith] extra pulls fastapi, starlette, and langsmith.

Exports

build_payment_app

The recommended entry point — returns a FastAPI app pre-wired with PaymentMiddleware. Mount the returned app in langgraph.json’s http.app field.

Signature

Why FastAPI and not plain Starlette

langgraph-api versions prior to a known internal OpenAPI fix crash on plain Starlette http.app wrappers — update_openapi_spec falls through to Starlette’s SchemaGenerator which YAML-parses internal endpoint docstrings and chokes on them. app.openapi() (FastAPI’s own generator) takes a clean path. build_payment_app returns a FastAPI app so users do not need to know about this upstream bug. The middleware class itself (PaymentMiddleware) is a starlette.middleware.base.BaseHTTPMiddleware and works on both Starlette and FastAPI — only the outer app wrapper matters.

Example

langgraph dev and langgraph up both honor the http.app field — the middleware composes around LangSmith Deployment’s built-in routes (/runs, /threads/{id}/runs, /assistants, etc.).

Lifecycle

The middleware implements the canonical x402 verify-then-work-then-settle ordering inside one HTTP cycle:
Agent exceptions propagate naturally to the ASGI runtime as 5xx — buyers are not charged for failed runs. Settle failures after a successful 2xx response are logged at ERROR and do not surface to the client (the buyer already received the value).

RouteConfig

Per-route pricing. Routes that don’t match an incoming request pass through ungated.

Fields

Route matching

Routes are keyed as "METHOD /path". Path parameters can use either Starlette :param or FastAPI/LangGraph {param} syntax — both match by position against the incoming request path. Examples that all match POST /threads/abc-123/runs/wait:
For LangSmith Deployment specifically, the only path that fits the verify-work-settle lifecycle in one HTTP cycle is POST /threads/{thread_id}/runs/wait (or its stateless counterpart POST /runs/wait). Background runs (POST /runs) return immediately; streaming runs (POST /runs/stream) lose streaming due to body buffering (see Limitations).

PaymentMiddleware (direct use)

build_payment_app is the recommended entry point. If you need a custom Starlette or FastAPI app (e.g. additional middleware, custom routes), use PaymentMiddleware directly:

Observability

When LANGSMITH_TRACING=true is set, the middleware opens a top-level nvm:x402-request trace per gated request, with nvm:verify and nvm:settlement child spans nested under it. Both child spans carry the same nvm.* metadata the decorator emits (plan_ids, scheme, network, payer, credits_redeemed, balance.after, tx_hash, payment_token abbreviated, verify/settle durations) — see the Observability section in LangChain Integration. The same metadata is also attached to the parent trace. The graph’s own LangGraph-emitted trace appears as a sibling top-level trace, not a child of nvm:x402-requestlanggraph-api initiates the graph trace at the graph-invocation boundary, independent of our middleware’s trace context. Verification failures (missing token, invalid signature, insufficient credits) raise PaymentRequiredError inside the verify_span so LangSmith marks the parent + child as failed via the canonical context-manager exit path. Settle failures after a successful 2xx mark the settle span as failed but leave the parent trace successful (matching the buyer-visible outcome).

Limitations

  • Streaming responses are buffered. The middleware reads the downstream response body in full before attaching the payment-response settlement header. SSE / /runs/stream endpoints become blocking-then-bulk. Gate /runs/wait only, or accept the trade-off.
  • Python only. LangSmith Deployment’s custom-app surface is documented as Python-only by LangChain. TypeScript variant tracked in the LangChain integration epic (TS-3).
  • Sync I/O in async dispatch. The four sync SDK calls (resolve_scheme, resolve_network, verify_permissions, settle_permissions) are wrapped in asyncio.to_thread(...) so they do not block the event loop. langgraph dev’s blocking-call detector treats unwrapped sync HTTP calls as fatal warnings; the wrapping is load-bearing.

See also

  • x402 Protocol for envelope and header semantics.
  • LangChain Integration for the @requires_payment decorator (tool-time gating).
  • payments_py.langsmith.spans for the observability helpers used internally (verify_span, settlement_span, attach_metadata_safely).