Product analytics
Sessions, page and screen views, and custom events from the web and app SDKs.
Product analytics records what people do in your app or site. It starts when RheoProvider mounts, including when no Rheo flow is on screen. Flow funnel events (flow_started, step_viewed, and the rest of the event catalog) stay on POST /v1/sdk/events. Engage track (web and React Native) stays on POST /v1/sdk/track. Product events use POST /v1/sdk/analytics/events and do not start automations.
Collection is on by default on the GA SDKs (web, Expo, bare React Native). Turn it off per platform:
| SDK | Opt out |
|---|---|
Web (@getrheo/react) | analytics={{ enabled: false }} on RheoProvider |
| Expo and bare React Native | analytics={{ enabled: false }} on RheoProvider |
| SwiftUI / Flutter | Coming soon — not customer-installable yet |
Consent
On the web, an omitted analytics.consent is pending. Rheo writes no anonymous id, session, or first-touch attribution, and it sends no product events, until the host grants consent. In the EU and UK that storage needs consent before it happens. Rheo does not ship a banner. Your site, usually through a consent management platform, decides when the visitor has accepted.
On web, set analytics.consent on RheoProvider:
consent | Behavior |
|---|---|
omitted or pending | Leave device storage untouched and send no product events until you grant consent. This is the default. |
granted | Collect immediately. |
denied | Same pause as pending, and delete any analytics id, session, and first-touch values already stored. |
analytics.enabled: false still turns collection off, including after a grant.
import { RheoProvider, setAnalyticsConsent } from '@getrheo/react';
export const App = () => (
<RheoProvider config={{ publishableKey, analytics: { consent: 'pending' } }}>
{children}
</RheoProvider>
);
// Call these from the consent banner. Earlier product events are dropped.
const onAccept = () => setAnalyticsConsent('granted');
const onReject = () => setAnalyticsConsent('denied');setAnalyticsConsent('granted') starts collection. setAnalyticsConsent('denied') stops it and clears storage. Product events recorded before a grant are dropped, not sent later.
While consent is pending or denied, the web SDK leaves these keys untouched:
rheo_app_user_idrheo_product_analytics_session_idrheo_product_analytics_session_last_atrheo_product_analytics_first_sentrheo_web_attribution_first_touch_v1
UTM parameters, the referrer, and the landing URL from the current page stay in memory. A grant on that page still records first touch. If the consent tool already stored a grant before paint, pass consent: 'granted' so the first page view is kept.
A flow can resolve before a grant. It uses an in-memory id and writes rheo_app_user_id only after consent is granted. App SDKs are unchanged.
Automatic events
session_startwhen there is no session, or the last product event was more than 30 minutes ago. The session id is stored on the device.first_visiton web, orfirst_openon app SDKs, once per persisted anonymousappUserId.page_viewon web for the first load and laterpushState,replaceState, andpopstatechanges. The page name is the path.
App navigators differ, so screen views are explicit. screen(name) records screen_view. On React Native, bindNavigationState(state) reads the focused route from a React Navigation state change and calls screen.
Custom events and user id
logEvent(name, properties?) records a custom event. The name is at most 120 characters. Properties are at most 32KB. setUserId(id) sets customUserId on later product events. The anonymous appUserId stays the device id.
Events batch in memory for about 5 seconds, or until 500 events, and flush when the provider unmounts. There is no offline disk queue. A retried batch with the same event ids is inserted once inside the deduplication window.
The first events of a session wait until attribution is known, or for 3 seconds, whichever comes first. Web waits for the page's UTM parameters. The app SDKs wait for the attribution provider. session_start and the first page or screen view then carry that source. If nothing arrives, the events send without it.
That snapshot stays for the rest of the session. A later attribution update, including a deep link, is used by the next session. On web, document.referrer is read only for the first session in a document. A session that starts later in the same document uses the current URL and ignores the sticky referrer. A reload that continues the same session keeps the landing hit.
Channel is derived when the report runs, from the referrer host, source, medium, platform, and the mobile organic flag. Visits stored before that flag existed are left out of Channel and Referrer. They still count in Visitors. A mixed range does not sum those two columns to the visitor total until the 5-year event TTL has dropped them.
Retention reads each person's first day from product_analytics_first_touch, an incremental rollup of product events. An empty source on session_start loses to the earliest event that has a source, campaign, country, or device. The rollup is kept after the 5-year raw-event TTL so the original first day stays.
Web
import { RheoProvider, logEvent, setUserId } from '@getrheo/react';
setUserId('user_123');
logEvent('workout_logged', { minutes: 30 });React Native
import { logEvent, screen, setUserId, bindNavigationState } from '@getrheo/react-native-expo';
setUserId('user_123');
logEvent('workout_logged', { minutes: 30 });
screen('Home');Pass the navigation state from onStateChange to bindNavigationState when you want automatic screen views. Bare React Native exports the same functions from @getrheo/react-native-bare.
SwiftUI and Flutter
SwiftUI and Flutter product-analytics APIs are coming soon. Use the React Native or web snippets above until those SDKs ship as customer-installable packages.
Dashboard
Open Analytics on the app to read visitors, sessions, acquisition, location, engagement, retention, and technology. How to use those reports is in Analytics. If ClickHouse cannot run a product analytics query, the API returns 503 product_analytics_unavailable. A range with no events is still an empty result.
CLI
The Rheo CLI reads the same product analytics with a workspace API key. Reads need analytics:read. Responses are JSON.
rheo analytics product help
rheo analytics product summary <appId> --env live --platform web
rheo analytics product attribution <appId> --dimension source --xf-country US,CADefault environment is live. Omit --start and --end for the last 7 UTC days. Omit --platform for all platforms. Omit --grain for day. --segment-id limits the read to one segment. live ignores the date range.
| Dashboard | CLI |
|---|---|
| Overview totals | summary |
| Overview chart | series |
| Overview ranked lists in one payload | overview |
| Online map and people | live |
| Acquisition table and chart | attribution, attribution-series |
| Engagement table and chart | engagement, engagement-series |
| Page and screen name totals | screens |
| Events table and chart | events, events-series |
| Recent rows for one event | recent --name <event> |
| Retention summary and breakdowns | retention |
| New and returning over time | retention-series |
| Cohort table | retention-cohorts |
| Return curve | retention-curve |
| Location table and chart | demographic, demographic-series |
| Technology table and chart | technology, technology-series |
| Visitor list | visitors |
visitors includes revenueUsd: net ledger gross for that person in the selected range. Renewals and refunds are included, so the number can be negative. Acquisition, entry page, and country revenue elsewhere in these reports is originating gross only.
Overview series points include revenue and revenueByProvider (revenuecat, superwall, stripe). The overview chart draws revenue as one bar beside the selected metric. summary includes revenue, the same originating gross for the Revenues tile. Pass revenueProvider to limit that gross to revenuecat, superwall, or stripe.
Breakdown commands require --dimension. Attribution: acquisitionChannel, referrer, source, campaign, medium, content, term, adset, creative. Technology: browsers, operatingSystems, devices. Location: country, region, city. Engagement: page, entry, exit. Retention drills take --dimension and --value together (source, campaign, country, platform, or device).
Column filters match the dashboard tables: --source, --campaign, --medium, --content, --term, --adset, --creative, --browsers, --operating-systems, --devices, --country, --region, --city, --page, --entry, --exit.
Cross filters repeat, or take commas: --xf-acquisition-channel, --xf-referrer, --xf-source, --xf-campaign, --xf-medium, --xf-country, --xf-region, --xf-browser, --xf-os, --xf-device, --xf-page, --xf-event, --xf-entry, --xf-exit.
One operator per dimension, set with --xf-op-<dimension>: is, is_not, contains, or does_not_contain. A missing operator means is, so xf_country=US stays an exact include. contains and does_not_contain are valid on campaign, page, event, entry, and exit. Contains is case-sensitive. is not and does not contain keep an empty value. Positive page and event operators list only the matching rows after the session qualifies. Negative operators exclude the session, and the table lists what the remaining sessions contain.
Run rheo analytics product help for the full flag list. Flow funnel metrics stay on rheo analytics <kind> <flowId>.