Skip to main content
Before writing tests, set up x-varianz-id propagation in each instrumented service. This is a one-time setup per service; afterwards, every VPoint you add works with test sessions automatically. The session ID must be:
  1. Extracted from incoming requests (HTTP header or gRPC metadata)
  2. Held in the execution context for the duration of the request
  3. Injected into all outgoing requests to downstream services
Step 3 is the one teams miss. If a service calls downstream services without forwarding the ID, downstream VPoints silently never see your test’s stages.
At your API gateway, strip or override x-varianz-id (and baggage) headers arriving from the public internet. Only test infrastructure should set them.

With OpenTelemetry Baggage

If your services already run OTel with the W3C Baggage propagator (most default setups include it), propagation across HTTP and gRPC hops is nearly free — carry x-varianz-id as a baggage entry and OTel’s instrumented clients and servers move it for you.
You still need to hand the extracted ID to the Varianz SDK (Varianz.set_session, varianz.WithSession, etc.) — the sections below show where.

Python

A gRPC server interceptor extracts the ID and activates session routing; handlers need no per-request code:
For HTTP, the same two lines go in middleware — @app.before_request in Flask, or an ASGI middleware in FastAPI that reads the x-varianz-id header and calls Varianz.set_session(...). Outbound: forward the current session on downstream calls.
The session is held in a contextvar, so it copies across asyncio tasks automatically. Varianz and OTel interceptors coexist without conflict.

Go

Set the session on the request context.Context with varianz.WithSession; both varianz.VPoint callables and proxy.Call read it from there.
Never spawn a goroutine without passing the parent context.Context — the session ID is lost with it.

TypeScript

The SDK holds the session in AsyncLocalStorage, exposed as VarianzContext. It survives await, timers, and event emitters.
For Fastify, Koa, and Hono, read the header in an onRequest/middleware hook and wrap the rest of the request in VarianzContext.session(...). For NestJS, nestjs-cls provides the same AsyncLocalStorage plumbing. gRPC (@grpc/grpc-js): server interceptors return synchronously before the handler runs, so they can’t wrap the async handler in the storage scope. Bind the session inside the handler instead:
If a method already receives the session as a parameter, declare it with varianzId('paramName') in the @vpoint params and the SDK enters the scope automatically.

Java and Kotlin

The JVM SDK pulls the session ID on every VPoint invocation through a single static method you provide, annotated @VarianzId @Function(global = true). Your framework interceptor stashes the incoming header in a request-scoped carrier; the annotated method reads it back. This one pattern works for gRPC, Spring MVC, WebFlux, and virtual threads — only the carrier changes. Step 1 — the binding class (ThreadLocal shown; see the carrier table below):
Step 2 — stash the header in an interceptor:
For Spring MVC, do the same in a HandlerInterceptor (preHandle sets, afterCompletion clears) and register it via WebMvcConfigurer. Step 3 — forward downstream:
For HTTP clients (RestTemplate, WebClient, OkHttp), an outbound interceptor reads sessionOverride() and sets the header the same way. Pick the carrier for your concurrency model. Plain ThreadLocal is correct for thread-per-request servlet and gRPC work, and wrong for reactive code: The annotated method runs synchronously on the VPoint caller thread, so it must read from a carrier that’s populated on that thread — don’t try to block() on a reactive context inside it.

Services with explicit header-forwarding lists

Some codebases forward a fixed set of headers at the application level (a getForwardHeaders()-style function listing x-request-id, x-b3-traceid, …) instead of using interceptors. If yours does, add x-varianz-id to that list — a one-line change that carries the session through the whole chain:
To find these, search for patterns like forward.*header, getForward, or existing tracing headers in the codebase.

Verify before writing tests

  1. Start a local registry and your services.
  2. Send a request with a fake session ID:
  3. Check each downstream service’s logs to confirm the ID arrived at every hop.
A stage that never applies in a multi-service test almost always means one hop in this chain is missing.