> ## Documentation Index
> Fetch the complete documentation index at: https://subtext.fullstory.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Statsig and Subtext: From a Signal to the Session

> Attach the Subtext session URL to Statsig, then jump from any Statsig signal — a tripped guardrail, a red scorecard, a suspicious exposure — straight into the exposed user's real session.

Statsig tells you *that* a variant moved a metric: a scorecard turned red, a guardrail tripped, a conversion dropped in the treatment. It can't tell you *why* one exposed person got stuck. Subtext can. Attach the Subtext session URL to your Statsig user object and every Statsig signal becomes a jumping-off point — your agent reads the `subtext_url`, opens that person's real session, and walks the decisive moments with screenshots, the component tree, network, and console. Statsig detects; Subtext diagnoses.

<Note>
  This guide assumes the [Subtext capture snippet](/docs/install/overview) is installed and capturing sessions, and the Statsig JS client (`@statsig/js-client`) is initialized.
</Note>

## Attach the session URL

Store the current Subtext session URL on your Statsig user object so it travels with the signal. There are two places to attach it, depending on how precise the link needs to be.

### On the user object

Attach the URL to the Statsig user's `custom` field via `updateUserAsync`. Custom fields ride along on every subsequent exposure and `logEvent` call, so they show up in the logs that back your experiments. Call it wherever your app already identifies the user — on auth resolution, not at module top level.

<CodeGroup>
  ```js analytics.js theme={null}
  const subtextUrl = FS('getSession', { format: 'url.now' })
  if (subtextUrl) {
    await client.updateUserAsync({
      ...client.getContext().user,
      custom: { ...client.getContext().user.custom, subtext_url: subtextUrl },
    })
  }
  ```

  ```tsx React theme={null}
  useEffect(() => {
    if (!user) return
    FS('setIdentity', { uid: user.id, properties: { email: user.email } })
    const subtextUrl = FS('getSession', { format: 'url.now' })
    if (subtextUrl) {
      client.updateUserAsync({
        userID: user.id,
        custom: { subtext_url: subtextUrl },
      })
    }
  }, [user])
  ```
</CodeGroup>

### On a specific event

When the exact moment matters, pass the URL directly on a single `logEvent` call. `url.now` carries a timestamp, so the link opens the replay at that instant — ideal for high-signal events like errors and conversion failures.

```js theme={null}
client.logEvent({
  eventName: 'checkout_failed',
  value: error.message,
  metadata: { subtext_url: FS('getSession', { format: 'url.now' }) },
})
```

<Warning>
  Don't read `FS('getSession')` at module top level — it returns `null` until the session has started, and don't call `updateUserAsync` before `client.initializeAsync` has resolved. When spreading into `updateUserAsync`, keep the rest of the user object (and existing `custom`) so you don't wipe targeting attributes. The session URL changes on a new session and when `FS('setIdentity')` is called with a different `uid`, so re-call `updateUserAsync` whenever you re-identify — the `custom` field is frozen at set-time. In event `metadata`, don't use dotted keys (`subtext.url`): Statsig parses `.` as a nested path and can null the value. Store `subtext_url`.
</Warning>

## From signal to session

However you first hear about a problem in Statsig, the path is the same: get to the `subtext_url`, then hand it to your agent for a Detect-vs-Diagnose read. The scorecard says the variant regressed; the session shows whether the new feature rendered correctly, errored, or simply confused people.

<AccordionGroup>
  <Accordion title="A guardrail or metric-drop Slack alert">
    Statsig posts to Slack: "⚠️ Guardrail `checkout_error_rate` triggered on experiment `new-cart-v2`, treatment group."

    Open the experiment and drill into the affected treatment users via Pulse / the Users tab. Inspect a recent exposure for one of them and copy `subtext_url` from the `custom` fields. Grab a few, then hand them to your agent:

    ```text theme={null}
    Statsig tripped a guardrail on experiment new-cart-v2 (treatment). Here are sessions from users
    who were exposed to the treatment and hit trouble:
    <paste 3–4 subtext_urls>

    Review them with a Detect-vs-Diagnose lens. The metric says treatment is worse — what does the
    variant actually do on screen for these users? Did the new cart render correctly, throw an error,
    or confuse them? One root-cause hypothesis and whether we should ship, hold, or roll back.
    ```
  </Accordion>

  <Accordion title="An experiment readout meeting">
    In the weekly experiment review, a variant's scorecard is red on a key metric and someone asks "but why?"

    In the Statsig Console, open the **Users** tab, enter a `userID` from the regressed cohort, and read `custom.subtext_url` off the most recent exposure.

    ```text theme={null}
    We're reviewing experiment <name>. The <variant> scorecard regressed on <metric>. Here's a
    session from a user in that variant: <paste subtext_url>

    Open it and tell me what the variant experience was actually like for this person. Is the feature
    behaving as designed? Any errors or broken UI that would explain the metric? I need a concrete
    "here's why" for the readout, not a guess.
    ```
  </Accordion>

  <Accordion title="A single suspicious exposure">
    You spot one user in the Events Explorer who got the treatment and had a clearly bad outcome.

    Open that log line and copy `subtext_url` from the user `custom` fields or the event metadata.

    ```text theme={null}
    One user in the treatment of <experiment> had a bad outcome. Session: <paste subtext_url>

    Walk me through what they saw in the variant. Did my feature render and behave correctly for
    them? Find the exact moment it went wrong and show me the UI + console there.
    ```
  </Accordion>
</AccordionGroup>

<Tip>
  For a regression spanning several exposed users, ask your agent to assemble one proof document (`doc-create`) with a screenshot per exposure and a single shared link — then paste that back into the experiment's notes or the readout deck so the scorecard number carries its explanation.
</Tip>

## For agents

An autonomous agent can run the whole loop against Statsig: discover the regressed variant, extract the `subtext_url`, and hand each session to the Subtext MCP. Statsig ships an official MCP server at `https://api.statsig.com/v1/mcp` (remote, HTTP), authenticated with a `statsig-api-key` header (a Console API key — read keys get read tools) or OAuth. Its tools front the Console API (experiments, gates, dynamic configs, layers, metrics, segments, autotunes), for example `Get_List_of_Experiments` and `Get_Experiment_Details_by_ID`. Docs: `docs.statsig.com/integrations/mcp`.

```bash theme={null}
claude mcp add --transport http statsig https://api.statsig.com/v1/mcp --header "statsig-api-key: <KEY>"
```

<Note>
  For this workflow the MCP is discovery-only: **no MCP tool returns exposure records or the user `custom` fields where `subtext_url` is stored**, and no documented Console-API endpoint returns a single exposure record by ID with its custom field — that value lives in the warehouse, not the Console API. Extraction is warehouse- or webhook-based (see step 2); the warehouse route requires Statsig's Enterprise tier — non-Enterprise projects must use the Event Webhook.
</Note>

<Steps>
  <Step title="Discover the regressed variant">
    Use the MCP tools (`Get_List_of_Experiments`, `Get_Experiment_Details_by_ID`) or the Console API directly — base `https://statsigapi.net/console/v1`, header `STATSIG-API-KEY`, version `20240601`:

    ```
    GET /console/v1/experiments                       # list experiments + status
    GET /console/v1/experiments/{id}                  # scores, config
    GET /console/v1/gates/{id}/rules/{ruleID}/pulse_results
      # per-metric directionality, % change, CI deltas, means, p-values → the regressed metric
    ```

    Near-real-time affected-user and event signals come from the **Event Webhook**, which forwards exposure, gate, and experiment events to your endpoint.
  </Step>

  <Step title="Extract the session URL">
    Read `subtext_url` from the warehouse exposures table (best fit) — e.g. `experimentation.statsig.exposures`. This table only exists with warehouse access to exposures, which is Enterprise-tier on Statsig: Data Warehouse Exports are Enterprise-only, and Warehouse Native is likewise part of the Enterprise tier. On Developer or Pro plans, capture `subtext_url` via the Event Webhook instead. The `user_dimensions` column is JSON of the user's attributes at first exposure:

    ```sql Databricks theme={null}
    SELECT unit_id, experiment_group, user_dimensions:subtext_url AS subtext_url
    FROM experimentation.statsig.exposures
    WHERE name = '<experiment>' AND user_dimensions:subtext_url IS NOT NULL;
    ```

    The JSON-extraction syntax varies by warehouse — e.g. `JSON_VALUE(user_dimensions, '$.subtext_url')` on BigQuery, `PARSE_JSON(user_dimensions):subtext_url` on Snowflake, `JSON_EXTRACT_PATH_TEXT(user_dimensions, 'subtext_url')` on Redshift.

    If you logged `subtext_url` on a custom event instead, it lands in that event's `metadata`.
  </Step>

  <Step title="Hand off to Subtext">
    For each URL, call `review-open(session_url=<subtext_url>)` on the Subtext MCP. Event-level URLs are moment-precise, so the review opens exactly when the signal fired.
  </Step>
</Steps>

<Note>
  `subtext_url` only round-trips if it was emitted into the exposure stream (as a user attribute) or onto a custom event at instrumentation time (see [Attach the session URL](#attach-the-session-url)). Event-level links are moment-precise; user- and exposure-level links open at session start.
</Note>

## Related

* [Session Review overview](/docs/session-review/overview) — what your agent does once a session is open.
* [Install the capture snippet](/docs/install/overview) — required before any session URL exists to attach.
