> ## 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.

# Hightouch and Subtext: From a Cohort to the Session

> Attach the Subtext session URL to Hightouch Events, then jump from any warehouse cohort — a campaign, a computed audience, a bad-event sync — straight into the user's real session.

Hightouch is a Composable CDP: it pipes events into your warehouse and activates audiences out into ad platforms, email, and CRMs. It tells you *who* a person is and *which* segment they fell into — not *what they experienced*. Subtext can. Attach the Subtext session URL to your Hightouch Events data and every cohort carries a jump-to-session link — 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. Your warehouse cohorts detect; Subtext diagnoses.

<Note>
  This guide assumes the [Subtext capture snippet](/docs/install/overview) is installed and capturing, and the Hightouch Events browser SDK is loaded — the `window.htevents` snippet or the npm `@ht-sdks/events-sdk-js-browser` package.
</Note>

## Attach the session URL

Store the current Subtext session URL on your Hightouch Events data so it travels into the warehouse as a column, ready to filter audiences and dashboards on. The Events browser SDK is modeled on the Segment `analytics.js` spec, so there are two places to attach it, depending on how precise the link needs to be.

### On the user profile

Attach the URL to the user with `htevents.identify`. It lands as a trait and, downstream, a column on the `identifies` table in your warehouse. 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) {
    htevents.identify(user.id, { 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) {
      htevents.identify(user.id, { subtext_url: subtextUrl })
    }
  }, [user])
  ```
</CodeGroup>

### On every event

Source middleware runs on every call, so it attaches the URL fresh per event. Because `url.now` carries a timestamp, the link deep-links to the moment each event fired. Event properties auto-convert into warehouse columns, so `subtext_url` becomes a column on the `track` and `page` event tables.

```js theme={null}
htevents.addSourceMiddleware(({ payload, next }) => {
  // Stamp both track and page events. Page views land in their own warehouse
  // table, so landing-page queries can filter on `subtext_url` too.
  if (payload.obj.type === 'track' || payload.obj.type === 'page') {
    const url = FS('getSession', { format: 'url.now' })
    if (url) payload.obj.properties = { ...payload.obj.properties, subtext_url: url }
  }
  next(payload)
})
```

Register the middleware once at initialization, before any `track` calls — not inside a component render.

<Warning>
  Don't read `FS('getSession')` at module top level — it returns `null` until the session has started. The URL changes on a new session and when the user re-identifies, so call `htevents.identify` on every auth state change to keep the profile trait current; the source middleware reads the URL fresh per event and never needs re-attaching. Pick `subtext_url` and use the same key across `identify` and `track` — warehouse columns and audience filters are case-sensitive. The signed URL grants replay access, so keep it in your warehouse and internal surfaces; never sync it into a third-party ad or email audience.
</Warning>

## From signal to session

However the cohort first surfaces — a campaign report, a computed audience, a sync check — the path is the same: get to the `subtext_url`, then hand it to your agent for a Detect-vs-Diagnose read. Hightouch Events stores the value as `subtext_url` on the profile and event tables in your warehouse.

<AccordionGroup>
  <Accordion title="A paid campaign that converts poorly">
    The growth marketer is watching a landing-page campaign. It drives clicks, but the landing page converts below plan. Hightouch captured the UTM parameters on the events (`context.campaign`), so the cohort is addressable.

    Query the events table for that campaign's sessions:

    ```sql theme={null}
    SELECT user_id, subtext_url, timestamp
    FROM hightouch_events.pages
    WHERE context_campaign_name = 'summer_launch'
      AND subtext_url != ''
    ORDER BY timestamp DESC
    LIMIT 20;
    ```

    Grab a few links, then hand them to your agent:

    ```text theme={null}
    Our `summer_launch` paid campaign converts well below plan on the landing page. Here are sessions
    for users who arrived from it but didn't convert:
    <PASTE 3-4 subtext_urls>

    Review them and find the common drop-off. Detect-vs-Diagnose: the data says they bounced — what does
    each session actually show on the landing page? One synthesized hypothesis at the end.
    ```
  </Accordion>

  <Accordion title="A user enters a failed audience">
    The lifecycle / CRM owner sees a user land in a computed audience like *"started checkout, no purchase in 24h,"* and wants to know *why* before sending a win-back.

    Open the user's warehouse profile, or query their most recent session link, and copy `subtext_url`.

    ```text theme={null}
    This user entered our "abandoned checkout" audience. Before I send a win-back, I want to know what
    actually happened. Session: <PASTE subtext_url>

    Open it and tell me: did they abandon by choice, or did something break/confuse them at checkout? If
    it's a bug, that changes the message I send. Walk me through the relevant moments.
    ```
  </Accordion>

  <Accordion title="A sync or data-quality check flags bad events">
    The marketing-ops or data engineer who owns the Hightouch source sees a sync surface malformed or missing properties on a key event, hinting at a broken client.

    Find a recent offending event that still carries `subtext_url` and copy it.

    ```text theme={null}
    Our Hightouch source shows malformed `Checkout Started` events from the web client. Here's a session
    where one fired: <PASTE subtext_url>

    Open it around that event. Is the client genuinely broken (bad state, JS error) or is the tracking
    just mis-wired? Show me the UI and console at that moment.
    ```
  </Accordion>
</AccordionGroup>

<Tip>
  For a campaign investigation across several users, ask your agent to consolidate the findings into one proof document (`doc-create`) — a screenshot per user and one shareable link — then drop it into the campaign retro or audience note so the next person sees the human story behind the segment.
</Tip>

## For agents

An autonomous agent can run the whole loop: discover the cohort, extract the `subtext_url`, and hand each session to the Subtext MCP. Hightouch does ship an [official MCP server](https://hightouch.com/docs/ai-integrations/mcp) — it connects your assistant to Hightouch's marketing agents for exploring your schema, building and saving audiences, and designing journeys — but it is **not documented as an event-history reader**, and it isn't enabled in every workspace. The events land in your warehouse, so the agent story runs through the warehouse's MCP. If that warehouse is ClickHouse, use the official `mcp-clickhouse` server (`list_databases`, `list_tables`, `run_query`, read-only by default). For Snowflake / BigQuery / Redshift, use that warehouse's own MCP or SQL API — the query shape is the same. Hightouch's REST API manages models and syncs, not event history, so it is the wrong surface for pulling session links.

<Steps>
  <Step title="Discover the cohort">
    Find the campaign, audience, or failing-event cohort worth watching. Because Hightouch captures UTM parameters, you can scope directly to a campaign. To triage a computed audience instead, join the audience membership table to the events table and pull each member's most recent `subtext_url`.
  </Step>

  <Step title="Extract the session URL">
    Read `subtext_url` off those rows with the warehouse's read query tool:

    ```sql theme={null}
    -- non-converting sessions from a specific paid campaign
    SELECT user_id, subtext_url, timestamp
    FROM hightouch_events.pages
    WHERE context_campaign_name = 'summer_launch'
      AND timestamp > now() - INTERVAL 7 DAY
      AND subtext_url != ''
    ORDER BY timestamp DESC
    LIMIT 25;
    ```
  </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 the Events SDK attached it at capture (see [Attach the session URL](#attach-the-session-url)); older rows carry an empty value, so filter `subtext_url != ''`. Table and column names depend on how your Hightouch source maps events into the warehouse — confirm them before hard-coding. Keep the warehouse MCP read-only; a review agent never needs write access.
</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.
