Store Referral Attribution
How store-specific referral tracking works across auth, portal, and analytics.
Overview
When a user signs up via a store partner’s referral link (?ref=store-{slug}-{hex}), the system:
- Persists the referral source on
User.referral_source(generic referral flow) - Detects the
store-prefix and records aStoreReferralevent (store-specific) - Fires a
STORE_REFERRAL_SIGNUPPostHog event for analytics - Shows store-level metrics in the partner portal (or via Integration API for external apps)
Data Model
Store — models/syntropy/store.py
One per partner. Created via Shopify app install (future) or seed data (dev).
| Field | Type | Notes |
|---|---|---|
owner_id | int FK → user.id | The partner user who owns this store |
slug | str unique | URL-safe store identifier |
referral_code | str unique | Format: store-{slug[:12]}-{4hex} |
category | StoreCategory | supplement_shop, health_food, online_wellness, etc. |
status | StoreStatus | pending → active → suspended/deactivated |
StoreReferral — models/syntropy/store.py
One row per referral event (click or signup).
| Field | Type | Notes |
|---|---|---|
store_id | int FK → store.id | Which store’s link was used |
referred_user_id | int | None FK → user.id | Null until user signs up |
referral_code | str | Denormalized for query performance |
status | str | clicked → signed_up → active |
utm_params | dict (JSON) | Preserved from the referral URL |
Attribution Pipeline
Step 1: Cookie Capture (landing page)
When a visitor lands with?ref=store-acme-a1b2, the referral code is stored in a browser cookie. This happens in the landing page JS — no state involvement.
Step 2: Auth Callback — states/shared/auth/_processes.py
handle_referral_cookie() fires after Clerk sign-up completes:
try/except so that a failure in store tracking never breaks the main signup flow.
Step 3: StoreReferral Record — functions/db_utils/store_referral.py
record_referral_signup_sync(referral_code, user_id):
- Looks up
Storebyreferral_code - If an unmatched
clickedrow exists → updates it withuser_idandstatus=signed_up - Otherwise → creates a new
StoreReferralwithstatus=signed_up
DB Utility Functions
All infunctions/db_utils/store_referral.py. All are synchronous (use rx.session()).
| Function | Purpose | Returns |
|---|---|---|
record_referral_click_sync(referral_code, source_url, utm_params) | Record anonymous link click | bool |
record_referral_signup_sync(referral_code, user_id) | Record user signup, link to click if exists | bool |
get_referrals_for_store_sync(store_id, limit=50) | Recent referral events for a store | list[dict] |
get_store_referral_stats_sync(store_id) | Aggregate stats | dict with keys below |
Stats dict shape
Consuming Referral Data
Current: Partner Portal (on hold)
Note: In-app partner onboarding is disabled in favor of Shopify app integration
(PRD-17). The partner portal remains present for assessment but is config-gated
via features.partner_portal. Store creation will be handled by the Shopify app
install flow, not an in-app registration form.
PartnerPortalState (states/partner/portal.py) resolves referral data on page load:
Computed vars for components
| Var | Type | Description |
|---|---|---|
store_data | dict | Full Store record (empty dict if no store) |
has_store | bool | Whether this partner has a Store entity |
referral_code | str | The active referral code (store or user) |
referral_link | str | Full URL: https://syntropyhealth.bio/?ref={code} |
referral_stats | dict | Stats dict (see shape above) |
total_signups | int | Shortcut for referral_stats["total_signups"] |
recent_signups | int | Shortcut for referral_stats["recent_signups_30d"] |
referred_users | list[dict] | List of referred user records |
Future: Integration API (PRD-17 Phase 6)
When the Integration API is built, the same DB utils power the outbound endpoints:Component example (portal, when enabled)
PostHog Events
| Event | Fired When | Properties |
|---|---|---|
REFERRAL_SIGNUP_COMPLETED | Any referral signup (generic) | referral_source |
STORE_REFERRAL_SIGNUP | Store-prefixed referral signup | referral_code |
STORE_REFERRAL_LINK_CLICKED | Store referral link clicked | (for future landing page use) |
STORE_REGISTERED | New store created | (for registration flow) |
AFFILIATE_REFERRAL_LINK_COPIED | Partner copies referral link in portal | referral_code |
utils/event_taxonomy.py and fired via utils/analytics.capture_event().
Referral Code Convention
Store referral codes follow the pattern:store-{slug[:12]}-{4hex}
Examples:
store-acme-supps-a1b2store-vitaminwor-f3e4
store- prefix is the discriminator. The existing validate_referral() in utils/referral.py accepts ^[a-zA-Z0-9_\-]+$ (max 64 chars), so store codes pass validation without changes.
Related Documentation
- State Management — Reflex state patterns (loading guards, background events)
- User Flows — Auth and onboarding flows