Skip to Content
ConceptsSurvey Lifecycle Events

Survey Lifecycle Events

Every platform emits a set of events as a survey moves through its lifecycle — see each platform’s own Events page for exact names and payload shapes, since those diverge (web’s survey:step vs. the natives’ survey:question_answered, object vs. bare-string payloads, and so on — Platform Parity has the full table). This page covers the design that’s shared across all of them.

Lifecycle events don’t re-trigger themselves

Internally, each SDK tracks its own lifecycle events (survey shown/dismissed/closed, question answered, survey completed) the same way it tracks any custom event — including adding them to user.eventHistory. But they’re excluded from trigger candidacy: without that exclusion, a wildcard survey (one with no triggerEventNames, which matches any event) would match its own survey_dismissed and immediately re-show itself. This exclusion only applies at the trigger-candidacy gate — if you deliberately write an EVENT_FREQUENCY condition against survey_completed in a Stage 2 rule, it still sees those occurrences normally.

on() returns a handle, not a promise to call off() correctly

on(event, callback) returns a Registration; call .cancel() on it to unsubscribe. This exists because callback-by-reference matching doesn’t work uniformly — Swift closures aren’t Equatable, so there’s no way to match “the same callback” by identity on iOS the way you can in JS. A returned registration, keyed by an internal id rather than the callback reference itself, works identically everywhere and makes cancel() idempotent: calling it twice, or after the listener already fired once, can’t accidentally remove a different registration that happens to share the same callback function.

Web additionally keeps off(event, callback) as a second mechanism (removes the first match by reference) — mainly for compatibility with code written before Registration existed.

Dispatch is snapshotted

If a listener cancels its own registration (or calls off()) synchronously from inside its own callback, that mutates the same listener list the dispatch loop is iterating. Every implementation snapshots the listener list before iterating, so a self-cancelling callback can’t shift a later listener into the just-removed slot and cause it to be skipped.

ready replays for late subscribers

The natives’ ready event fires once initialize fully settles. A listener that registers after that point still receives it — a “replay latch” remembers that ready already fired and invokes a late subscriber’s callback immediately, rather than requiring you to race the subscription against initialization. Web has no ready event at all — see each platform’s Events page for what to use instead.