Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesEvent TrackingEvent Tracking — Behavior

Event Tracking — Behavior

State machine

Stateless. Every track is independent.

track(distinctId, event, props) ├── logger.log(event, props) (always, all envs) └── if (NODE_ENV === 'production' && POSTHOG_API_KEY): client.capture({ distinctId, event, properties }) ├── batched in SDK (flushAt=20 OR flushInterval=10s) └── caught try/catch → logger.error on failure

Invariants

InvariantEnforcement
No-op in non-prodConstructor checks NODE_ENV === 'production'
Missing API key disables sends but keeps loggerConstructor branch
Capture errors never throwtry/catch around client.capture
Distinct ID is the canonical user UUIDCaller responsibility; verify usage at call sites
Pending events flushed on shutdownonModuleDestroy → client.shutdown()

Golden paths

Coach publishes an announcement

  1. AnnouncementsService.createAnnouncement finishes.
  2. Calls this.tracking.track(userId, 'announcement_published', { announcement_id, org_id, priority }).
  3. Logger emits info log.
  4. In prod, PostHog SDK queues the event; flushes within 10s.

Payment funnel

  1. Payment domain emits multiple track calls (payment_initiated, payment_succeeded, payment_failed).
  2. PaymentObservabilityService delegates the PostHog capture to this service so the Sentry + log fan-out stays local.

Edge cases

ScenarioBehavior
PostHog API unreachableCaught; logged; events lost (no retry queue in this layer)
App SIGKILL’d before flushPending events lost
Distinct ID is missing / nullPostHog still accepts (with $anonymous fallback); call sites should always pass user.id
Properties include large objectsPostHog rejects payloads > some limit (~512KB); no client-side enforcement
Same event captured twiceBoth sent; PostHog has no native dedup

Side effects

ActionLogsSends to PostHog
track (any env)yesonly in prod with key
identify (any env)no (no log line in current code)only in prod with key
onModuleDestroyflushes pending events

No DB writes. No queue. No events emitted internally.

Permissions

System-internal — no HTTP surface.