Open the console

First-session drop-off: find the step, not the average

A drop-off rate averages several different failures. Count seen, clicked and completed per step, and the losing step names itself.

First-session drop-off: find the step, not the average

Every onboarding report has one number in it: the share of new users who did not finish. It is the least useful number on the page. A drop-off rate is an average of several different failures — a step nobody sees, a label nobody understands, an action nobody can complete, a list that is simply too long. You cannot fix an average; you can fix a step. Knowing which one takes more than one count per step.

Three counts per step, not one

A step in a checklist produces three separate facts, and most funnels collapse them into one:

  • Seen — the step was put in front of the person.
  • Clicked — they went after it.
  • Completed — the thing the step describes actually happened.

Each gap between two consecutive counts isolates a different problem. Seen to clicked falling: the label, the order or the link is wrong. Clicked to completed falling: the action is harder than the label promised, or the completion was never reported. Seen staying at zero: the step never reaches anyone, which is not a copy problem at all.

One count per step tells you the list lost people. Three tell you where, and which fix applies.

What the instance already records for you

Two of the three counts already exist. It is worth knowing which two before adding anything.

The project stats call — GET /api/gs/v1/admin/projects/{id}/stats — returns subjects, completions, completedAll, dismissed, dismissRate and perStep. perStep is the useful one: completion rows are unique per person and per step, so a number there is the users who finished that step, not the clicks it received. Read it down the list and you have the funnel in step order; the first step that sits below the one before it is where the next look belongs.

The widget also reports a small event stream — widget_opened, step_clicked, step_completed, widget_dismissed, completed_all — each carrying the step key. That is your second count, as events rather than totals.

What does not exist is seen. There is no per-step impression: the panel being opened is recorded, a step being rendered is not. The honest sentence about your funnel today is that it carries two of the three counts, and the missing one separates "nobody reads the list" from "everybody reads it and nobody does it". Put that sentence in your dashboard instead of a percentage nobody measured.

Add the missing count in your own app

That third count belongs in your analytics: only your app knows a step was put in front of someone. The widget dispatches a composed DOM event for each transition, so wiring it up is a listener, not an integration.

<script>
  const gs = document.querySelector('getting-started')

  gs.addEventListener('gs:opened',         () => track('onboarding_list_opened'))
  gs.addEventListener('gs:step-clicked',   (e) => track('onboarding_clicked',   { key: e.detail.key }))
  gs.addEventListener('gs:step-completed', (e) => track('onboarding_completed', { key: e.detail.key }))
  gs.addEventListener('gs:dismissed',      (e) => track('onboarding_dismissed', { scope: e.detail.scope }))

  // per-step "seen" is yours to report: call this when a step's row is rendered
  // in the screen the user is actually looking at
  function reportStepSeen(key) {
    track('onboarding_step_seen', { key })
  }
</script>

Use the same step keys you configured, and keep them stable — they are the join between your analytics and the console. Once a key carries seen, clicked and completed, the arithmetic between the three is the diagnosis. Keep the click and the completion apart when you wire this: a click means someone went looking, and going looking is not evidence of finding anything.

Read the funnel largest loss first

With three counts per step, resist comparing percentages. Order the steps by the absolute number of people lost between one count and the next, largest first, and fix the top of that list. A step that loses a hundred people deserves more attention than one that loses five, however bad the second one's ratio looks — and the largest absolute loss is usually early, because that is where the most people still are.

Two cross-checks keep the reading honest:

  • Does the last step match your activation event? If the list ends somewhere other than the moment a new account first gets value, your funnel measures the checklist rather than the product. Pick the activation event first.
  • Are the calls arriving at all? The instance exposes /metrics, with request counts per route and gs_completions_total by source (client, server, import). A step that shows clicks and no completion, week after week, is more often a completion call that never fires than a step that is too hard.

Four failure modes, four different counts

  • Seen near zero. Nobody ever meets the step: it sits below the fold, behind a dismissible peek, or behind a prerequisite the first session cannot satisfy. Reorder or delete before rewriting a word of copy.
  • Seen high, clicked low, on an early step. The label is not a sentence about the user's next action. This is the one failure a copy change fixes on its own.
  • Clicked high, completed low. The label overpromised, or the completion never gets reported — an action_url pointing at the wrong screen, or a click treated as a completion in your code. Check gs:step-clicked and gs:step-completed for the same key.
  • Every step healthy and still nobody finishes. The list is too long, or the last step is not something the user came to do. Cut steps rather than optimise them.

A dismissal is a signal, not a failure

The stats call keeps the two dismissals apart: hiding the panel until the next visit, which is not stored, and the permanent one, which is stored against the person. Read dismissRate next to the funnel rather than inside it. Many temporary dismissals on a list people still finish means the panel is in the way, not that the checklist is wrong. A permanent rate that keeps climbing says the list is asking for more than the user came to do — and a tour would have hidden that signal entirely, because a tour has no state to record.

One change, one baseline, one date

No public number says what your completion share ought to be, so inventing one is worse than having none: your own pre-change figure is the only baseline that means anything. Write down the current per-step counts, the definition of the first session you are counting in — the same calendar day, or the first twenty-four hours — and the date you will read the counts again. Then change a single step and compare the same counts over the same window. If the number moves, you learned something about that step. If you changed three things, you learned nothing.

You can watch the counts move on a draft before any of it is live: configure the list you have, walk it yourself, and read the funnel in the console. The response shape of the stats call, perStep included, is in the API reference.

Try it on your app