feat(analytics): enrich PostHog person profiles and capture first-touch attribution - #224
Open
AminDhouib wants to merge 1 commit into
Open
AminDhouib wants to merge 1 commit into
AminDhouib wants to merge 1 commit into
Conversation
…ch attribution PostHog only ever knew a person's email, so a human opening a profile could not tell who the shopper was, on what surface/version, or where they came from. identify now sends a full $set (email, display name, rc_app_user_id, app_id, app_version, platform, environment, locale, timezone, user_created_at) plus a $set_once acquisition story (signup_date, first platform/app_version, first-touch utm_*/ref/gclid/fbclid, referrer domain, landing path). First-touch attribution is banked on the very first page load into localStorage (caramel.first_touch) and never overwritten, so a later organic session cannot rewrite the campaign a user originally arrived from. It is captured before the capture-target check, so an attribution-bearing landing still survives a deploy with analytics disabled. The payload builders are pure and unit-tested: they drop every nullish, empty and placeholder value, keep the distinct_id as the internal UUID, and never fall back to the email for a display name. A payload fingerprint stops an unchanged profile being re-sent on every React commit, and every call is wrapped so an analytics failure reports to Sentry instead of reaching a render.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Opening a PostHog person profile for a Caramel shopper told you one thing: their email. Not who they are, not which surface or build they are on, not where they came from. This lands section B of the fleet-wide Identity Enrichment spec for
caramel-app.What PostHog now receives
posthog.identify(userId, $set, $set_once)— built inapps/caramel-app/src/lib/analytics/identityProperties.ts, called fromidentity.tsidentifyUser, which thePostHogClientProviderdrives off the Better Auth session.$set(current truth, safe to re-send)email$emailnamename, elsefirstName+lastName, elseusername. Never the email.rc_app_user_idapp_idAPP_ID(caramel)app_versionAPP_VERSIONplatformwebenvironmentproduction/e2e)localenavigator.languagetimezoneIntl.DateTimeFormat().resolvedOptions().timeZoneuser_created_atcreatedAt, ISO-8601$set_once(acquisition story, written once by PostHog)signup_date(account creation, else first identify on this page load),first_platform,first_app_version,first_utm_source,first_utm_medium,first_utm_campaign,first_utm_term,first_utm_content,first_ref,first_gclid,first_fbclid,first_referrer_domain,first_landing_path.Super properties —
app_id,app_version,environment,platformwere already registered ininitPosthogBrowserand are unchanged.First-touch attribution
New module
apps/caramel-app/src/lib/analytics/firstTouch.ts. On the first page load it recordsutm_*,ref,gclid,fbclid, the external referrer hostname and the landing path intolocalStorageundercaramel.first_touch, and never overwrites that record — a later organic session cannot rewrite the campaign a user originally arrived from. Two deliberate choices:initPosthogBrowser, so an attribution-bearing landing is still banked on a deploy where PostHog capture is disabled.first_landing_pathwould never exist for an organic visitor, and a later campaign visit would be mislabelled as their first touch.A same-origin referrer resolves to nothing rather than recording ourselves as the acquisition source.
Safety properties
distinct_idstays the internal user UUID. Email is a property only, andresolveDisplayNameexplicitly refuses to fall back to it.compactPropertiesdrops nullish values, empty/whitespace strings, the literal stringsundefined/null, non-finite numbers and non-scalars. No placeholders ever reach a profile.resetPosthogIdentityclears it so the next login re-sends.identifyUserand the first-touch reads/writes are wrapped; failures go toconsole.errorplusSentry.captureExceptionwith a coarseoperationtag and are swallowed.Skipped, and why
plan_tier/subscription_status/subscription_expires_at(both as person properties and as a super property): Caramel has no billing of any kind — no Stripe, no subscription or plan model inprisma/schema.prisma, no premium concept in the app. There is no source for these values, so they are not sent rather than sent empty.posthog.setPersonPropertieson a plan change has no trigger to hook to for the same reason.phone: not stored on the user.auth_provider: the Better Auth client session does not expose the account provider, and fetching it would mean an extra authenticated request on every identify. Left out rather than paid for; it would be cheap to add if the provider is ever surfaced on the session.apps/caramel-extension: has no PostHog integration at all (noposthogreference anywhere in the package), so there is no identify path to enrich there.Base branch
Opened against
main, notdev.devis 93 commits behindmainwith nothing of its own (git rev-list --left-right --count origin/dev...origin/main→0 93), it is not the repo default branch, and the entiresrc/lib/analytics/module this change extends does not exist on it.Verification
npx tsc --noEmit(caramel-app):TypeScript: No errors foundnpx vitest run tests/unit/firstTouch.test.ts tests/unit/identityProperties.test.ts:Test Files 2 passed (2),Tests 23 passed (23)npx eslinton the six changed files: cleannpx oxlintonsrc/lib/analytics: exit 0npx knip(caramel-app): cleannpx prettier --checkon the changed area:All matched files use Prettier code style!No E2E/browser run, per the task.