Core concepts

A short tour of the model behind userGist. If you understand these six words, the rest of the docs read themselves.

Mental model

Workspace → App → Users → Events → Segments → Prompts. Everything else (surveys, push, requests, in-app, analytics) hangs off this spine.

Workspace

The top-level container. A workspace owns:

  • A team (the humans, with roles)
  • A billing relationship (plan, usage, invoices)
  • One or more apps (your products / environments)

You typically have one workspace per company. Create more if you have distinct legal entities or strict data separation requirements.

App

An app is where your end-users live. Concretely it owns:

  • Write keys that authenticate your SDK
  • Push credentials (APNs .p8 for iOS, FCM service-account JSON for Android)
  • Users, events, segments, prompts, surveys, in-app messages, push campaigns, feature requests, analytics

Most teams use separate apps per environment: Acme Production and Acme Staging. Builds for multiple platforms can share one app when they should use the same audience, campaigns, and analytics. Split iOS, Android, React Native, or Flutter into separate apps only when you want independent data, credentials, campaigns, or key rotation.

Workspace overview showing the apps in the sidebar.
Workspace overview. The sidebar app card is the switcher.

User

A user is anyone who has opened your app while the SDK is initialised. Two states:

StateWhenStable ID
AnonymousBefore you call identifyanonymous_id (generated and stored locally)
IdentifiedAfter identify(userId, props, subjectToken)Your user_id (e.g. your DB primary key)

When you identify, userGist links the anonymous device session to the user ID. All prior anonymous events get retro-attributed.

// Anonymous: events get bucketed under an anonymous_id
UserGist.track('app_opened')
 
// Mint this token on your authenticated backend.
const { subjectToken } = await yourBackend.getUserGistSubjectToken()
 
// Now identified — same device, same user from here on.
await UserGist.identifyAsync('user_42', { plan: 'pro' }, subjectToken)
UserGist.track('checkout_completed', { amountUsd: 49 })
Calling `reset()` rotates the anonymous ID

Use UserGist.reset() on logout. It clears the cached user, generates a fresh anonymous_id, and prevents two accounts on the same device from being conflated.

Event

An event is a behavioural fact: user X did Y with these properties at time T.

UserGist.track('subscription_upgraded', {
  fromPlan: 'trial',
  toPlan: 'pro',
  monthlyUsd: 19,
})

Events are queued client-side and flushed in batches (every 15 s by default, or when the queue hits the batch size). They're stored in ClickHouse for analytics and used by the targeting engine to decide who's eligible for a campaign.

Two kinds of properties:

  • User properties (set on the user, persist across sessions) — sent via identify.
  • Event properties (specific to one event) — sent as the second argument to track.

See Features → Events for the schema registry, which lets you declare each event up-front and validate at ingest.

Segment

A segment is a reusable audience, defined by a DSL of property matchers and event-behaviour matchers.

Examples:

  • Power usersplan = 'pro' AND count(event = 'session_started', last 7 days) >= 5
  • Trial day 3count(event = 'trial_started', all time) >= 1 AND days_since(event = 'trial_started') = 3
  • Churn riskplan = 'pro' AND count(event = 'session_started', last 14 days) <= 1

Segments are evaluated:

  • Server-side when scheduling campaigns or computing rosters.
  • Client-side by the SDK to decide instantly which prompt/in-app to show, using the same evaluator (@usergist/sdk-core/evaluateSegment). That's how surveys can fire immediately on an event without a network round-trip.

See Features → Segments for the full DSL.

Prompt, Survey, In-app message, Push, Request

These are the channels — the things you actually deliver to your user. They share three building blocks:

  1. Targeting — a segment that decides who.
  2. Trigger — an event, a schedule, a deep-link, or on-demand.
  3. Content — what to render or send.
ChannelWhat it is
PromptSingle-question feedback (NPS, rating, thumbs). Fires inline on an event.
SurveyMulti-step questionnaire with branching, save-resume, deep-link.
In-app messageModal, slide-up or full-screen takeover with CTA buttons.
PushAPNs / FCM notification, scheduled or event-triggered.
Feature requestAn entry on your public roadmap, submitted by a user from in-app.

Every channel writes a response/delivery record back into userGist, which feeds analytics and unlocks lifecycle hooks like onResponse.

Write key

The credential that lets the SDK talk to the ingest API. It is per-app, environment-labelled, rotatable, and shown in plaintext exactly once. The API uses it to route traffic to the correct workspace and app. It does not choose which SDK runs—the package installed by the developer already determines that.

Write keys are client identifiers and are expected to be extractable from a mobile binary. They cannot prove a product user's identity or access dashboard data. See Admin → Write keys for rotation, scoping and rate limits.

userGist treats consent as a first-class input. You pass it via setConsent({ analytics, feedback, push, survey }) and the SDK gates each subsystem accordingly. No consent? No events queued.

await UserGist.setConsent({
  analytics: true,
  feedback: true,
  push: false,
  survey: true,
})

See Guides → GDPR & consent for the recommended flow.

What's next