# Installing analytics is not getting out of the building

*2026-08-20*

> Lean Startup said get out of the building. Most launches I watch run a survey or install Mixpanel and call that enough, while the path the customer already walked still has no name, time, or count on it.

[Lean Startup](https://theleanstartup.com/book) said [get out of the building](https://en.wikipedia.org/wiki/Customer_development). The launches I watch treat a survey or a dashboard as enough. The customer already walked a path through the product: signup, first value, the action they repeat, the stop they drop off at. None of those stops have a name in the telemetry.

Installing [Mixpanel](https://mixpanel.com/) is not knowing the customer. Tagging the journey is. Until each stop carries a name, a time, and a count, the warehouse is a pile. It's not a story about how they use the product.

I haven't wired this as a discipline on a product I own. I've watched enough launches skip it to know the shape of the miss. I fall in love with the solution, ship the thing I designed, and reach for feedback I can read in a spreadsheet while the path they already walked goes untagged.

That's the work:

1. name the path I believe they walk
2. fire a tag at each stop: time, count, success or fail
3. read the sequence, not the [vanity total](https://en.wikipedia.org/wiki/Vanity_metric)
4. change the product where the path stalls

## A survey and a tag tell different stories

[Ries](https://www.ericries.co/)'s line was never "pick one channel and stop." It was: stop sitting in the building with the solution I love, and go see what the customer does. A survey answers a real set of questions. Did this feel worth it? What were they trying to do? What would they miss if it disappeared? I want those answers. They're not the same answers the path gives.

A feedback form tells me what someone remembers, chooses to say, and has words for. A tag tells me what they did: which stop they reached, how long it took, how often they came back, where they dropped off. One is stated preference. The other is behavior. Both belong in the stack. The miss I keep seeing is treating the form as a stand-in for the path, as if a good question replaces watching the walk.

The industry objection writes itself. Product analytics has been around for more than a decade. Mixpanel, [Amplitude](https://amplitude.com/), [Google Analytics](https://marketingplatform.google.com/about/analytics/). Of course something is being tracked. The miss isn't the tool, and it isn't the survey either. It's that neither got wired to the journey I claimed they walk. Pageviews. A homepage bounce rate. A weekly active number that cannot tell me which stop died. A survey at the end that cannot tell me they never got past setup.

When the path is untagged, the form is the only instrument that got a name. The rest of the walk is empty.

```svg
<svg viewBox="0 0 760 196" role="img" aria-labelledby="journey-form-title journey-form-desc" xmlns="http://www.w3.org/2000/svg">
  <title id="journey-form-title">Feedback on an untagged path</title>
  <desc id="journey-form-desc">They arrived, set up, got value, and came back with no tags. The survey is the only instrument that got a name.</desc>
  <path d="M116 64 L186 64 M186 61 L190 64 L186 67" fill="none" stroke="var(--ink-muted)" stroke-width="1.25" stroke-linecap="butt" stroke-linejoin="round"/>
  <path d="M266 64 L336 64 M336 61 L340 64 L336 67" fill="none" stroke="var(--ink-muted)" stroke-width="1.25" stroke-linecap="butt" stroke-linejoin="round"/>
  <path d="M416 64 L486 64 M486 61 L490 64 L486 67" fill="none" stroke="var(--ink-muted)" stroke-width="1.25" stroke-linecap="butt" stroke-linejoin="round"/>
  <path d="M566 64 L636 64 M636 61 L640 64 L636 67" fill="none" stroke="var(--ink-muted)" stroke-width="1.25" stroke-linecap="butt" stroke-linejoin="round"/>
  <g class="diagram-mark">
    <circle cx="76" cy="64" r="22" fill="transparent"/>
    <g transform="translate(58 46) scale(1.5)" fill="none" stroke="var(--ink-muted)" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round">
      <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
      <circle cx="9" cy="7" r="4"/>
      <line x1="19" x2="19" y1="8" y2="14"/>
      <line x1="22" x2="16" y1="11" y2="11"/>
    </g>
    <text x="76" y="122" text-anchor="middle" font-family="var(--font-sans)" font-size="15" font-weight="500" fill="var(--ink)">They arrived</text>
    <text x="76" y="144" text-anchor="middle" font-family="var(--font-sans)" font-size="12" fill="var(--ink-muted)">no arrival tag</text>
  </g>
  <g class="diagram-mark">
    <circle cx="226" cy="64" r="22" fill="transparent"/>
    <g transform="translate(208 46) scale(1.5)" fill="none" stroke="var(--ink-muted)" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round">
      <path d="M10 5H3"/>
      <path d="M12 19H3"/>
      <path d="M14 3v4"/>
      <path d="M16 17v4"/>
      <path d="M21 12h-9"/>
      <path d="M21 19h-5"/>
      <path d="M21 5h-7"/>
      <path d="M8 10v4"/>
      <path d="M8 12H3"/>
    </g>
    <text x="226" y="122" text-anchor="middle" font-family="var(--font-sans)" font-size="15" font-weight="500" fill="var(--ink)">They set up</text>
    <text x="226" y="144" text-anchor="middle" font-family="var(--font-sans)" font-size="12" fill="var(--ink-muted)">no completion tag</text>
  </g>
  <g class="diagram-mark">
    <circle cx="376" cy="64" r="22" fill="transparent"/>
    <g transform="translate(358 46) scale(1.5)" fill="none" stroke="var(--ink-muted)" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round">
      <path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
      <path d="M20 2v4"/>
      <path d="M22 4h-4"/>
      <circle cx="4" cy="20" r="2"/>
    </g>
    <text x="376" y="122" text-anchor="middle" font-family="var(--font-sans)" font-size="15" font-weight="500" fill="var(--ink)">They got value</text>
    <text x="376" y="144" text-anchor="middle" font-family="var(--font-sans)" font-size="12" fill="var(--ink-muted)">no time-to-value tag</text>
  </g>
  <g class="diagram-mark">
    <circle cx="526" cy="64" r="22" fill="transparent"/>
    <g transform="translate(508 46) scale(1.5)" fill="none" stroke="var(--ink-muted)" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round">
      <path d="m2 9 3-3 3 3"/>
      <path d="M13 18H7a2 2 0 0 1-2-2V6"/>
      <path d="m22 15-3 3-3-3"/>
      <path d="M11 6h6a2 2 0 0 1 2 2v10"/>
    </g>
    <text x="526" y="122" text-anchor="middle" font-family="var(--font-sans)" font-size="15" font-weight="500" fill="var(--ink)">They came back</text>
    <text x="526" y="144" text-anchor="middle" font-family="var(--font-sans)" font-size="12" fill="var(--ink-muted)">no repeat tag</text>
  </g>
  <g class="diagram-mark">
    <circle cx="676" cy="64" r="22" fill="transparent"/>
    <g transform="translate(658 46) scale(1.5)" fill="none" stroke="var(--code)" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round">
      <rect width="8" height="4" x="8" y="2" rx="1" ry="1"/>
      <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
      <path d="M12 11h4"/>
      <path d="M12 16h4"/>
      <path d="M8 11h.01"/>
      <path d="M8 16h.01"/>
    </g>
    <text x="676" y="122" text-anchor="middle" font-family="var(--font-sans)" font-size="15" font-weight="500" fill="var(--ink)">They got asked</text>
    <text x="676" y="144" text-anchor="middle" font-family="var(--font-sans)" font-size="12" fill="var(--ink-muted)">the only instrument named</text>
  </g>
</svg>
```

Surveys are good at the why, the felt pain, the feature request in plain language. They're weak at the corridor: the screen they skip, the workaround they run twelve times a week, the stop where half the cohort disappears before anyone answers. That story lives in the path. I still run surveys. I just don't ask them to do a job only behavior can do.

## Every stop gets a name, a time, and a count

A tag is not a pageview. A tag is a named stop on the path I claimed they walk, with two numbers attached: how long they spent on this fragment, and how often they come back. Time is duration. Not the date the event fired. The warehouse will stamp a clock because that's how events work. The number I read is milliseconds on this stop, and milliseconds to the next milestone.

Success or fail rides on the same stop, because a stop that fires and then dies is a different story from a stop that never fires.

| Stop | Tag | Question it answers |
| --- | --- | --- |
| arrived | `signup` | did they get in? |
| first success | `first_value` | how long to value? |
| [the job they hired the product for](https://en.wikipedia.org/wiki/Jobs_to_be_done_framework) | `core_action` | do they repeat this? |
| the job failed | `core_action_failed` | where it dies? |
| they left | last stop that fired | where they disappear? |

I name the stop as the outcome, not the click. `export_clicked` tells me they touched a button. `report_exported` tells me the product did the job. [Time to value](https://en.wikipedia.org/wiki/Time_to_value) is the outcome. A click is theater.

```ts
capture('first_value', {
  stop: 'report_exported',
  duration_ms: Date.now() - stopEnteredAt,
})
```

The fail event carries the same `stop`, so I can read success and death as one path, not two dashboards:

```ts
capture('core_action_failed', {
  stop: 'report_exported',
  duration_ms: Date.now() - stopEnteredAt,
  reason: error.code,
})
```

`duration_ms` is how long this fragment took. Time to the next milestone is the same shape, across stops: `first_value` minus `signup`. How long they sat here. How fast they moved. A stop they clear in seconds is doing the job. A stop that holds them for forty minutes is the ticket.

Count without duration is a popularity contest. Duration without count is one long session from one curious person. Together they are a pattern: this is where they live, this is where they stall, this is what they hired the product for. I don't need a model for that sentence. I need the events to exist.

Once the feed is thick, the same events are what a clustering job or a propensity model would train on. Users who never hit `first_value` by day two. A feature that fires a lot and fails a lot. A path that always detours around the screen I designed as the happy path. That is a query on tagged stops. It isn't a science project. I haven't trained the model. I would not start there. I would start with the path.

## The warehouse is not the map

The capture layer is a solved problem. I don't care which warehouse, as long as it stores named events I defined, not a pile of pageviews I inherited.

| Job | Where it usually lives |
| --- | --- |
| named events, funnels, retention | [PostHog](https://posthog.com/), [Mixpanel](https://mixpanel.com/), [Amplitude](https://amplitude.com/) |
| website pageviews | [Google Analytics 4](https://support.google.com/analytics/answer/10089681) |
| clicks captured for me | [Heap](https://www.heap.io/), [PostHog autocapture](https://posthog.com/docs/product-analytics/autocapture) |
| the tape of a session | [FullStory](https://www.fullstory.com/), [Microsoft Clarity](https://clarity.microsoft.com/), [Hotjar](https://www.hotjar.com/), [LogRocket](https://logrocket.com/) |
| the same event in more than one warehouse | [Segment](https://segment.com/), [RudderStack](https://www.rudderstack.com/) |
| a crash on the same stop | [Sentry](https://sentry.io/) |
| a flag or an experiment on the same stop | PostHog, [Statsig](https://www.statsig.com/), [LaunchDarkly](https://launchdarkly.com/), [GrowthBook](https://www.growthbook.io/) |

Google Analytics is the one most teams already have. It answers which URL got traffic. It does not answer which job in the product succeeded. GA4 can take events. Most installs I see never fire them. A Mixpanel project with no taxonomy is the same miss in a nicer UI.

Autocapture is a bigger pile, not a map. Heap and PostHog will record every click. That is useful the day I forgot to tag a stop. It is not a substitute for naming the journey before launch. [Session replay](https://en.wikipedia.org/wiki/Session_replay) is the tape. I pull it when an event looks wrong, not to understand ten thousand users. Sentry is the failure tag when the stop is a crash. If the event name in Sentry and the event name in Mixpanel are different words for the same death, I'm back to stitching by hand.

The install is not the work. The work is the taxonomy: every stop I believe they walk has a name, and that name is the same in the product, the warehouse, and the crash report.

## Usage is the story the form does not tell

A feature can be well designed, on-brand, and still be used as a corridor to something else. Or never used. Or used once, in a panic, the day before a deadline, which is a different product than the one in the [Figma](https://www.figma.com/) file. A survey can tell me the export felt slow. It cannot tell me they skip the dashboard every morning and live in the export. People cannot narrate a pattern they don't know they have.

That's the whole point of tagging the path. I want to see the corridor, the dead room, the room they never leave. Taste is how I design the stop. Behavior is how I find out what the stop actually is.

The pretty feature that nobody repeats is not a design failure I can see in a screenshot. It's a count that stays at one. The ugly workaround they run twelve times a week is the job they hired the product for. The feed shows me that. The form might confirm it later, in words I can quote in a meeting. It won't surface it first.

## Time to value and dead features are already in the feed

If `first_value` carries how long they spent getting there, time to value is already a number. I don't need a kickoff about onboarding copy to find out they sat in setup for a day. A long duration is the path asking for work. A short one is the stop doing the job. If the event never fires, they never got value.

If `core_action` is the only stop with a high count, that is where the next work goes, even if the roadmap is in love with a different screen. If a stop I shipped last quarter still has a count of zero, that is a candidate to kill, not a candidate for a nicer empty state. If `core_action_failed` keeps pace with `core_action`, I know which feature is valuable and also broken, which is the worst kind of miss: people want it, and it dumps them.

Those are product decisions. Shorten the path to `first_value`. Stop spending design on a stop that never fires. Fix the stop that fires and fails. None of them require a model. They require the tags to have been there at launch. That's the part that keeps not happening, because by then I'm in love with the solution and the warehouse is "already set up."

People keep saying data is gold. Gold is not the Mixpanel bill. Gold is a path with names on it, read as a story about how they use the product, next to the story they chose to tell in a survey.

## Takeaways

- Pageviews are not a journey. I write the path as named stops, then I fire those events.
- Time is how long they spent on that fragment, not the date the tag fired. A long duration is a stall. A short one is a stop that's working.
- A click count is theater. I tag `report_exported`, not `export_clicked`.
- Weekly active users cannot locate a stall. I read the sequence, and the stall becomes the ticket.
- A feature at count zero is not an empty-state problem. I cut it, or I follow the corridor they actually use.
- If `first_value` never fires, they never got value. I shorten that path before I rewrite the copy.
- A crash in Sentry and silence in Mixpanel is two names for the same death. I use one.
- A survey tells me what they say. Tags tell me what they did. I need both, and I stop asking either to do the other's job.

None of that is a tooling failure. Each one is a path that already happened, left untagged or misread, while I sat in the building with the solution I loved.
