Core concepts
A short tour of the model behind userGist. If you understand these six words, the rest of the docs read themselves.
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
.p8for 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.

User
A user is anyone who has opened your app while the SDK is initialised. Two states:
| State | When | Stable ID |
|---|---|---|
| Anonymous | Before you call identify | anonymous_id (generated and stored locally) |
| Identified | After 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 })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 users —
plan = 'pro' AND count(event = 'session_started', last 7 days) >= 5 - Trial day 3 —
count(event = 'trial_started', all time) >= 1 AND days_since(event = 'trial_started') = 3 - Churn risk —
plan = '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:
- Targeting — a segment that decides who.
- Trigger — an event, a schedule, a deep-link, or on-demand.
- Content — what to render or send.
| Channel | What it is |
|---|---|
| Prompt | Single-question feedback (NPS, rating, thumbs). Fires inline on an event. |
| Survey | Multi-step questionnaire with branching, save-resume, deep-link. |
| In-app message | Modal, slide-up or full-screen takeover with CTA buttons. |
| Push | APNs / FCM notification, scheduled or event-triggered. |
| Feature request | An 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.
Consent
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.