Instrumenting an insurance client-360 app: modules, filters, teams, logins and geography

Analytics products are designed around shops: a visitor arrives, browses, adds to a cart, pays. An insurer’s client-360 application is nothing like that. Its users are employees who log in every morning, move between a search page, client and broker dashboards, policy pages, claims and renewals, filter by division and line of business, read the AI insights on a client, export to Excel and, occasionally, create a quote. The people funding it want to know whether it is being used, by whom, for what — and they want it on one page every week.

This post is the instrumentation that answers those questions, taken from the guide we give the application’s developers. Everything in it is live in the Insurance 360 (demo) project, which carries 30 days of exactly this shape, so every number below is one you can reproduce.

Automatic versus named

One snippet (or npm install @insight/clickiq) captures the mechanics with no code: pageviews on every React Router navigation, clicks with the element’s text and position, rage and dead clicks, scroll depth and time on page, form timings (never values), JavaScript errors, Core Web Vitals, sessions, device and browser, and session replay with inputs masked. What the SDK cannot know is your product’s vocabulary. It sees /clients/C-10729; it does not know that is the Client dashboard for Northgate Logistics, a Property account. Those moments you name.

Event taxonomy for an insurance client-360 app: what the SDK captures, what the app names, what the server sendsTHE SDK, AUTOMATICALLYTHE REACT APP, BY NAMETHE PYTHON BACK ENDMechanicspageview on every route changeclick · rage_click · dead_clickscroll · page_leave (time on page)form_field · form_submit · abandonerror · Core Web Vitalssession_start, browser, devicereplay, inputs maskedBusiness momentsModule viewed — moduleSearch — entity_type, resultsEntity viewed — entity_type, entity_name, lobFilter applied — filter, valueAI insight viewed — insight_typeExport — format · Quote created — lobidentify(id, manager, team, division, …)What only it knowsLogin succeeded — methodLogin failed — method, reasonPolicy bound — premium (value)geo from the branch,because the VPN hideswhere people sitOne project: Insurance 360same batch endpoint, joined by user_id; traits queryable as user.<trait>Executive usage report14 panels, AI summary, PDFAsk, segments, alerts“who fails to log in?” by user.nameHeatmaps, forms, journeysfrom the mechanics, unchanged
Three sources, one project. The SDK sends the mechanics, the React app names the business moments and says who the user is, and the back end sends what only it knows. Every report panel, question and segment reads the combined stream.

The taxonomy

Event names are sentences in title case, properties are snake_case, and values come from closed lists. That last rule matters more than it looks: the report breaks down by these strings, so Property and property become two bars.

EventPropertiesAnswers
Module viewedmodule: Search, Client dashboard, Broker dashboard, Executive dashboard, Policy page, Claims, Renewals, ReportsWhich modules are used, by whom
Searchentity_type: client, broker, policy · resultsSearch volume; zero-result searches
Entity viewedentity_type, entity_id, entity_name, lob, productTop clients and brokers; products and lines
Filter appliedfilter: country, product, lob, division, department, branch, state · valueWhich filters people actually use
AI insight viewedinsight_type: renewal_risk, cross_sell, claims_trend, broker_performanceWhich insights are read, per team
Exportformat: pdf, xlsxExports per module and user
Quote createdlob, product, entity_idConversion: sessions that led to a quote
Policy bound (server)premium (also sent as value), lob, productBound premium by team, branch, line
Login succeeded / Login failed (server)method: sso, password · reason: bad_password, locked, mfa_timeoutDaily actives; failed-login rate; who fails repeatedly
identifyname, email, manager, team, division, department, branch, roleEvery breakdown by person or team

Quote created and Policy bound are marked as the project’s conversion events, so sessions that produce one are flagged converted and journeys, zones and lift work against them.

The React side

A small loader module loads the SDK only when a key and host are configured and makes every helper a no-op otherwise, so the application never depends on analytics being reachable. Two helpers do most of the work. identify runs after every login, because traits change:

identify(me);   // id = stable employee id, never the email
// sends: name, email, manager, team, division, department, branch, role

Modules come from a route table, one event per route change (the SDK sends the pageview for the same navigation itself):

const MODULES = [[/^\/search/, "Search"], [/^\/clients\//, "Client dashboard"],
  [/^\/brokers\//, "Broker dashboard"], [/^\/policies\//, "Policy page"],
  [/^\/claims/, "Claims"], [/^\/renewals/, "Renewals"], [/^\/reports/, "Reports"]];
useEffect(() => {
  const module = MODULES.find(([rx]) => rx.test(pathname))?.[1];
  if (module) track("Module viewed", { module, path: pathname.replace(/\/[A-Z]-?\d[\w-]*/g, "/:id") });
}, [pathname]);

Filters send one event per filter in the applied set, debounced so a user adjusting three dropdowns sends three events, not thirty. Insights send an impression when one is rendered in view, once per insight per page. And four attributes in the JSX handle privacy and naming: data-clickiq-block around claims notes and documents (never captured, removed from replay), data-private on a rendered policyholder name (masked), data-clickiq-form on the quote form, and data-clickiq-name="Export" for a stable heatmap zone.

The Python side: what the browser cannot see

Three things only the server knows. A failed login never reaches a logged-in page, and the reason — a bad password, a locked account, an MFA timeout — is on the server. A bound policy and its premium come from the binding service. And the user’s real location: the application sits behind a VPN, so every browser reaches the analytics endpoint from one exit, and the map would show one dot.

A sender of about forty lines posts to the same ingest endpoint on a daemon thread with a four-second timeout, never on the request path, keyed by the same user_id the React app identifies with:

clickiq_service.track(attempted_user.id, "Login failed",
                      {"method": "password", "reason": "bad_password"}, branch=attempted_user.branch)
clickiq_service.track(user.id, "Policy bound",
                      {"premium": policy.premium, "lob": policy.lob, "product": policy.product},
                      value=policy.premium, branch=user.branch)

The branch argument maps to a country, region and city (Dallas, Chicago, Atlanta, Toronto, London, Sydney) and is sent as geo on the event, which wins over edge headers and the browser’s time zone. That is what puts Chicago and Dallas, not the VPN exit, at the top of the report’s city panel.

Privacy, by construction

No policyholder data goes into events. entity_name is a commercial client or a broker firm, a business; for personal lines, send the account id and leave the name out. Search queries are the likeliest leak (people type names and policy numbers), so the guide sends the entity type and result count and the query only after a privacy review says yes. Employee traits are workforce data, fine for an internal tool with the usual notice; keep them out of a customer-facing project. Retention is per project, default 395 days.

Thirty days of it

Events — Event definitions
The Event definitions tab for Insurance 360: 90,275 events across 18 event types in 30 days, listing page leave, scroll, pageview, click, Module viewed, vital, Filter applied, Entity viewed, AI insight viewed, Search, session start, Login succeeded, identify, Export, Quote created, Policy bound, Login failed and dead click with counts and users
Automatic and named events side by side. The typed rows (pageview, click, scroll, vital, page_leave) came from the SDK with no code; the rows marked custom are the taxonomy above.
90,275events in 30 days
120people, each identified
11,119Module viewed events
109failed logins, 51 people

With traits on every event, the questions an operations lead actually asks become one query each. These came from POST /api/query — Ask AI produces the same specs from the sentence:

QuestionQuerySpec (abridged)Answer, 30 days
Why do logins fail?Login failed by props.reasonMFA timeout 71 (38 people), locked 31 (25), bad password 7 (6)
How do people sign in?Login succeeded by props.methodSSO 1,448, password 254
Who fails most?Login failed by user.nameOmar Castillo 9, Lara Ibrahim 7, Beatriz Reyes 6
Which manager’s team is most active?$any by user.managerPriya Natarajan: 29,046 events, 25 people; next Mei-Lin Chen 14,866, 20
Which AI insights get read?AI insight viewed, users, by props.insight_typeRenewal risk 118, claims trend 115, cross-sell 113, broker performance 106
What do people search for?Search by props.entity_typeClients 1,214, brokers 490, policies 299
Where is premium bound?Policy bound, sum of value, by props.lobCasualty $1.71M (62 policies), Workers Comp $543K, Marine $445K

The login row is the one that tends to start a conversation. Two-thirds of failures are MFA timeouts, not wrong passwords — a usability problem with the second factor, not a security one — and a handful of people account for a disproportionate share. That is an IT ticket with a list of names attached, produced by a breakdown nobody had to build a dashboard for.

Events — Stream
The raw event stream for Insurance 360: one employee's session showing page leave, scroll, AI insight viewed, Filter applied, Apply filters click, Module viewed and pageview events on the client dashboard, then Entity viewed on a client page and Quote created on a policy page
One employee’s morning, event by event. Each named event sits next to the automatic ones from the same navigation: Module viewed beside its pageview, Filter applied beside the click on Apply filters.

What it feeds

With this taxonomy in place, the executive usage report template keeps all fourteen of its panels and renders with no further configuration: an AI-written summary, top users, daily actives, top clients and brokers, four filter panels, module engagement and geography. The executive report post shows what it wrote about last week.

Reports — Filters used
The Filters used section of the executive report: four horizontal bar charts for division led by Commercial, department led by Underwriting, line of business led by Property, and branch led by Dallas
One Filter applied event, four panels. Each is the same event broken down by value, filtered to one filter. A typo in a value would be a fifth bar here, which is why the values come from closed lists.

The mechanics feed the rest of the product unchanged: heatmaps and zones on the client dashboard (is the Export button found?), form analytics on the quote form, journeys from search to quote, signals when errors spike after a release, and agents that can be asked “which teams adopted the renewal-risk insight this month?” and answer from the same events.

Generic web analyticsPageviews by URL

/clients/C-10400 466 views. Which module, which client, which team, which line of business, and why did 51 people fail to log in? Not in the data.

A named taxonomyModules, entities, filters, teams

Client dashboard 3,753 views; Northgate Logistics the most viewed client; Priya Natarajan’s team the most active; MFA timeouts behind most failed logins. Each a one-line query.

Traits also make audiences cheap. A segment such as “Claims Operations, mobile” is a filter on user.team plus a device, saved once and usable in every chart, alert, session list and AI answer — and it can be described in a sentence and drafted by the AI against the project’s real traits before anyone saves it.

A checklist for your developers

  1. Install the loader; confirm the first event on the project’s Sites & apps page.
  2. Call identify after every login with the stable employee id and the traits; reset on logout.
  3. Add the module route table and the named events, with values from closed lists.
  4. Send logins, bound policies and geo from the back end.
  5. Wrap anything a policyholder would recognise in data-clickiq-block or data-private.
  6. Mark Quote created and Policy bound as conversion events.
  7. Create the executive usage report from its template and compare it with the demo, panel by panel.

The SDK sees paths and clicks. Your product has modules, clients and teams. Name the second, and every AI feature can talk about your business in its own words.

← All posts   Request a demo