- Prisma: posthog_person_cache (24h TTL), posthog_group_cache, session_custom_events
- SessionMeta: +posthogDistinctId, +groupKey, +customEventCount
- PostHog client: listSessionEvents, getPerson, getGroup
- posthog-cache.ts: cache-with-stale-refresh for person + group properties
- event-taxonomy.ts: dual-mode alias map (vin_decoded↔vin_decode_initiated,
vin_decode_success↔vin_decode_succeeded, vin_decode_error↔vin_decode_failed,
payment_success↔payment_succeeded). 56 tracked event names total.
- enrich.ts: per-session custom events fetch, persist to session_custom_events,
attach user_properties + group_properties via cache.
- tagger.ts: 14 new custom-event tags
(vin_decode_fail_pattern, provider_reliability_issue, payment_friction,
payment_ui_silent_failure (P0), payment_failed_session, checkout_abandonment,
downgrade_pending, compatibility_quality_gap, parts_export_abandoned,
search_validation_friction, onboarding_stuck, upgrade_hesitation,
power_user_path, at_risk_active_session, provider_mismatch,
api_key_friction, webhook_setup_struggle); group-context priority bump.
- heuristic.ts: customEventPromoteReasons() — 10 custom-event promote signals.
- compress.ts: merge_rrweb_and_custom_events interleaving with ⤷ EVENT: prefix,
±1.5s grouping, standalone flush for events with no nearby rrweb. Adds
company_context header line; key_events lists top-5 custom event counts.
- /insights page: +Events column. /insights/sessions/[id]: collapsible custom
events table + group key display.
Graceful: when Sase.tr emits 0 custom events the pipeline falls back to
rrweb-only tagging from Phase 6a v1.1.
PRD: behavioral-insight-pipeline-prd-1.md (v1.2),
sase-posthog-events-prd.md (Sase.tr-side, separate codebase).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
50 KiB
Sase.tr — PostHog Event Taxonomy & Implementation PRD
Proje Kodu: SASE-PH-001
Pilot: Süper Panel Behavioral Insight Pipeline (BIP-001) Phase 6 Prerequisite
Doküman Türü: Implementation PRD (Sase.tr codebase work)
Durum: Draft v1.0
Sahip: Founder (Thinxtra SaaS Studio)
Hedef Branch: feature/posthog-event-expansion
İçindekiler
- Vizyon ve Bağlam
- Hedefler ve Hedef Olmayanlar
- Mevcut Event Inventory ve Değerlendirme
- Identification Strategy (User + Group)
- Yeni Event Inventory
- Event Schema Standardı
- Mevcut Event İyileştirmeleri
- Data Privacy ve KVKK Uyumu
- Implementation Plan
- Testing Strategy
- Migration ve Backward Compatibility
- PostHog Dashboard Setup
- Documentation Standardı
- Riskler
- Karar Logu
1. Vizyon ve Bağlam
Bu PRD Neden Var
Süper Panel Behavioral Insight Pipeline (BIP) Phase 6 pilot'u Sase.tr üzerinde çalışacak. Pilot başlangıcında pipeline'ın aşağı yukarı 4 katmandan oluşacağı planlandı: ingestion → tagging → compression → LLM. Ancak gerçek değer üretmek için giriş katmanındaki sinyal kalitesi belirleyici.
Sase.tr'de mevcut PostHog event yapısı iskelet olarak hazır ama insight pipeline için 6 kritik alanda gap var:
- VIN decode performance context yok (timing, retry, provider detail)
- Parts/compatibility layer'da neredeyse hiç event yok
- Multi-provider fallback visibility yok
- Search behavior pre-submit görünmez
- Feature discovery pattern'leri yok
- User identification + group analytics belirsiz
Pipeline bu gap'ler kapatılmadan başlatılırsa:
- Insight'lar generic ve actionable olmaz
- LLM hallucination riski artar (eksik context'i tahminle doldurmaya çalışır)
- Cost-per-validated-insight hedefi tutturulamaz
- KVKK compliance riski (data-private audit yapılmadan)
Kapsam
Bu PRD sadece Sase.tr codebase work'tür. Süper Panel tarafı (ingestion, compression, LLM) ayrı PRD'de (BIP-001) ele alınır.
İçerik:
- Identification flow audit ve update
- 35+ yeni event eklemesi (3 fazda)
- 8 mevcut event'in property zenginleştirmesi
data-privateattribute audit- PostHog dashboard ve funnel kurulumu
Bağımlılık Zinciri
[Bu PRD - Sase.tr Event Work]
↓ (output: zengin event stream)
[BIP Phase 6a - Ingestion + Filter]
↓
[BIP Phase 6b - LLM Analysis MVP]
↓
[Insight pipeline value]
Bu PRD'nin Faz 0 (blocker) kısmı tamamlanmadan BIP Phase 6a başlatılmamalı. Faz 1 (high-ROI) ise BIP Phase 6a ile paralel yürüyebilir.
2. Hedefler ve Hedef Olmayanlar
Hedefler
| # | Hedef | Ölçü |
|---|---|---|
| G1 | BIP pilot'un yüksek-kalite insight üretmesi için gereken event'leri eklemek | Event taxonomy %100 dokümante, 0 ad-hoc capture |
| G2 | B2B account-level analiz için group analytics kurulumu | posthog.group('company') tüm authenticated session'larda çalışır |
| G3 | User cohort segmentation kapasitesi | identify() çağrısı plan_tier, signup_date, company_id, total_queries içerir |
| G4 | KVKK uyumlu sensitive data handling | Tüm PII alanları data-private veya PostHog mask config'i ile korunur |
| G5 | Mevcut event'lerin geriye dönük kırılmaması | Production funnels/dashboards bozulmaz |
| G6 | Faz 0 (blocker) 2 gün içinde tamamlanır | Phase 6a başlangıç tarihini bloklamaz |
| G7 | Tüm yeni event'ler tek bir taxonomy kaynağında dokümante | docs/posthog-events.md tek doğruluk kaynağı |
Hedef Olmayanlar
- PostHog SDK migration: posthog-js / posthog-node mevcut versiyonlar kalır, upgrade ayrı iş
- PostHog → diğer analytics geçişi: Bu PRD'de PostHog tercihi sabit
- Tüm autocapture event'lerinin manual'a dönüştürülmesi: PostHog autocapture (pageview, autocapture, rageclick) korunur
- Backend event coverage'ın frontend'e eşitlenmesi: Bazı event'ler doğal olarak sadece frontend (UI interaction) veya sadece backend (transaction confirmation)
- Mobile app event coverage: Sase.tr mobile yok, varsa ayrı PRD
- Real-time event processing: PostHog ingestion latency'si kabul edilir
- A/B test infrastructure: Bu PRD'de feature flag setup yok (BIP Phase 6d'de ele alınır)
- Custom property dictionary'nin uluslararasılaştırılması: Property anahtarları İngilizce kalır
3. Mevcut Event Inventory ve Değerlendirme
3.1 Mevcut Event Dökümü
Aşağıdaki event'ler şu an Sase.tr'de production'da gönderiliyor:
Auth & Kullanıcı Yaşam Döngüsü
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|---|---|---|---|
user_signed_up |
method: "email" | "google" |
_auth/register.tsx:41,78 |
✅ İyi durumda |
user_logged_in |
method: "email" | "google" |
_auth/login.tsx:42,65 |
✅ İyi durumda |
user_logged_out |
reason?: "account_deleted" |
dashboard.tsx:148, settings-content.tsx:136 |
✅ İyi durumda |
VIN Decode (Search)
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|---|---|---|---|
vin_decoded |
vin |
search.tsx:142 |
⚠️ Naming muğlak (initiated mi success mi?), context fakir |
vin_decode_success |
vin, vehicle_id, source? |
search.tsx:166, index.tsx:529 |
⚠️ Performance context yok, source belirsiz |
vin_decode_error |
vin, error, source? |
search.tsx:174, index.tsx:534 |
⚠️ Error code yok, retry context yok |
vin_decode_candidates |
(çoklu eşleşme modal) | search.tsx:157 |
⚠️ Candidate sayısı/yapısı yok |
vin_decode_candidate_selected |
(seçim) | search.tsx:225 |
⚠️ Hangi candidate seçildi (index, confidence) yok |
Subscription / Pricing Funnel
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|---|---|---|---|
plan_selected |
plan |
subscription/index.tsx:326 |
⚠️ Context fakir (price, period?) |
yearly_toggle_clicked |
period: "monthly" | "yearly" |
:1039, 1050 |
✅ |
checkout_started |
plan, period |
:344 |
⚠️ Coming-from context yok |
trial_started |
yok | :936 |
⚠️ Plan offered yok |
subscription_cancelled |
yok | :897 |
⚠️ Cancellation reason yok |
subscription_resumed |
yok | :737 |
⚠️ Time-away yok |
cancel_flow_viewed |
yok | :876 |
✅ |
cancel_save_clicked |
yok | :268 |
⚠️ Hangi save offer kabul edildi? |
downgrade_offer_shown |
yok | :364 |
⚠️ Which offer? |
downgrade_offer_accepted |
yok | :264 |
⚠️ Which? |
downgrade_offer_declined |
yok | :827 |
⚠️ Which? |
Trial Urgency Banner (FN-306)
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|---|---|---|---|
trial_urgency_banner_viewed |
yok | subscription/index.tsx:430, trial-urgency-banner.tsx:77 |
⚠️ Days left to trial end yok |
trial_urgency_banner_cta_clicked |
yok | :492, :97 |
⚠️ Same |
trial_urgency_banner_dismissed |
yok | :501, :87 |
⚠️ Same |
Payment
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|---|---|---|---|
payment_initiated |
method: "iyzico" | "eft", plan, period, amount |
payment-content.tsx:196,202 |
✅ İyi |
payment_success |
(dual: FE+BE) | payment-content.tsx:73,135, payments.service.ts:122,214 |
⚠️ Dual-capture clarify gerekli |
payment_failed |
(dual: FE+BE) | payment-content.tsx:76, payments.service.ts:130,237 |
⚠️ Failure reason zayıf |
receipt_uploaded |
payment_id |
payment-content.tsx:209 |
✅ |
Schema / Parts
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|---|---|---|---|
oem_code_copied |
oem_code, part_id, ... |
parts-panel.tsx:52 |
⚠️ Tek parts event'i, layer'ın geri kalanı görünmez |
Social Proof
| Event | Properties | Kaynak | Pipeline Değerlendirmesi |
|---|---|---|---|
social_proof_impression |
page, section? |
_auth.tsx:23, index.tsx:572,589,606, subscription/index.tsx:456,473 |
✅ |
social_proof_engaged |
page, section? |
_auth.tsx:33, index.tsx:616, subscription/index.tsx:483,487 |
✅ |
3.2 Genel Değerlendirme
Güçlü yönler:
- Subscription/pricing funnel disiplinli (downgrade offer zinciri özellikle değerli)
- VIN decode için ayrı success/error/candidates ayrımı doğru pattern
- Auth events çift kanal değil (sadece frontend, duplicate yok)
- Payment dual-capture infrastructure değerli (reliability için), kullanımı netleşince güçlenir
Genel gap'ler:
- 11 event'in 0 properties olması (boş capture)
- VIN decode family'sinde performance/context fakirliği
- Parts layer'da single event (Sase.tr'nin core value layer'ı)
- Provider multi-source görünmezliği
- Identification + group analytics belirsizliği
3.3 Identification Audit (Önce Doğrulanmalı)
Bilinmeyenler — Faz 0 öncesi audit gerekli:
posthog.identify(userId, properties)çağrılıyor mu? Nerede?- Hangi user property'leri set ediliyor?
posthog.group('company', companyId)çağrılıyor mu? (Büyük ihtimalle hayır, çünkü event listesinde company_id görünmüyor)- Anonymous → authenticated geçişte
posthog.alias()çağrılıyor mu? posthog.reset()logout'ta çağrılıyor mu?
Bu beş soruyu Faz 0 başlangıcında 30 dakikalık codebase audit ile cevapla.
4. Identification Strategy (User + Group)
4.1 User Identification
Hedef: Her authenticated session başlangıcında posthog.identify() çağrılır ve aşağıdaki property setlenir/güncellenir.
User Properties Schema
type PostHogUserProperties = {
// Identity (set once, $set_once kullan)
signup_date: string; // ISO 8601
signup_method: 'email' | 'google';
// Mutable state (set every login, $set kullan)
email_domain: string; // "domain.com" (full email değil — PII)
plan_tier: 'free' | 'starter' | 'brand_specific' | 'full';
plan_period: 'monthly' | 'yearly' | null;
subscription_status: 'trial' | 'active' | 'cancelled' | 'past_due' | 'none';
trial_ends_at: string | null; // ISO 8601
// B2B
company_id: string | null;
company_role: 'owner' | 'admin' | 'member' | null;
// Behavior aggregates (periyodik update, daily cron)
total_vin_queries: number;
total_vin_queries_last_30d: number;
preferred_provider: 'PL24' | 'PCAT' | 'RMEX' | 'TecDoc' | null;
last_query_at: string | null;
total_logins: number;
days_since_signup: number;
// Feature flags / preferences
language: 'tr' | 'en';
timezone: string; // "Europe/Istanbul"
has_api_keys: boolean;
has_webhooks: boolean;
// Locked properties (asla değişmez)
user_id_internal: string; // Sase.tr internal ID (same as distinct_id genellikle)
}
Önemli kurallar:
- Email asla ham olarak set edilmez (sadece
email_domain) $set_onceile setlenen property'lersignup_date,signup_method$setile her identify'da update edilenler diğerleritotal_vin_queriesgibi aggregate'ler backend'den periyodik update (her event'te güncelleme PostHog hızını yavaşlatır)
Identify Çağrı Pattern'i
// frontend: src/lib/posthog/identify.ts
import posthog from 'posthog-js'
import { hashEmail } from '@/lib/crypto'
export function identifyUserToPostHog(user: User, company: Company | null) {
posthog.identify(user.id, {
// $set_once
$set_once: {
signup_date: user.createdAt,
signup_method: user.signupMethod,
},
// $set (every call updates)
$set: {
email_domain: user.email.split('@')[1],
plan_tier: user.subscription?.tier ?? 'free',
plan_period: user.subscription?.period ?? null,
subscription_status: user.subscription?.status ?? 'none',
trial_ends_at: user.subscription?.trialEndsAt ?? null,
company_id: company?.id ?? null,
company_role: user.companyRole ?? null,
total_vin_queries: user.stats.totalQueries,
total_vin_queries_last_30d: user.stats.queries30d,
preferred_provider: user.stats.preferredProvider,
last_query_at: user.stats.lastQueryAt,
total_logins: user.stats.totalLogins,
days_since_signup: daysSince(user.createdAt),
language: user.language,
timezone: user.timezone,
has_api_keys: user.apiKeyCount > 0,
has_webhooks: user.webhookCount > 0,
user_id_internal: user.id,
},
})
// Group identification - B2B için kritik
if (company) {
posthog.group('company', company.id, {
name: company.name,
tier: company.tier,
seat_count: company.seatCount,
seats_used: company.seatsUsed,
industry: company.industry ?? 'unknown',
country: company.country ?? 'TR',
signup_date: company.createdAt,
mrr_usd: company.mrrUsd,
total_queries_last_30d: company.stats.queries30d,
})
}
}
Identify Trigger Noktaları
| Trigger | Çağrı |
|---|---|
| Login başarılı | identifyUserToPostHog(user, company) |
| User profile update | identifyUserToPostHog(user, company) |
| Subscription change | identifyUserToPostHog(user, company) |
| Company tier change | identifyUserToPostHog(user, company) |
| Logout | posthog.reset() ← önemli, anonymous distinct_id yenilenir |
| Anonymous → authenticated geçiş | posthog.alias(user.id, posthog.get_distinct_id()) çağrısı ardından identify |
4.2 Group Analytics (B2B Backbone)
posthog.group('company', companyId, properties) çağrısı PostHog'a "bu session bir company context'inde yapılıyor" der. Bu kurulum B2B insight kalitesini ikiye katlar.
Company Properties Schema
type PostHogCompanyGroupProperties = {
name: string;
tier: 'free' | 'starter' | 'brand_specific' | 'full';
seat_count: number;
seats_used: number;
industry: string;
country: 'TR' | 'DE' | 'others';
signup_date: string; // ISO 8601
mrr_usd: number; // monthly recurring revenue
total_queries_last_30d: number;
primary_use_case: 'parts_dealer' | 'auto_service' | 'fleet_manager' | 'unknown';
// Computed nightly (admin DB job)
active_user_count_last_7d: number;
active_user_count_last_30d: number;
churn_risk_score: number; // 0-100, BIP pipeline'a değerli
}
Group Identification Trigger'ları
Her authenticated event sırasında posthog-js otomatik olarak son group'u event'e ekler. Yani bir kez posthog.group() çağrılırsa, sonraki tüm event'ler bu company context'inde gider.
Kritik nokta: Multi-tenant kullanıcılar (1 user, multiple companies) varsa company switch sırasında re-group çağrılmalı:
function switchCompany(newCompanyId: string) {
// ... company switch logic
posthog.group('company', newCompanyId, companyProperties)
posthog.capture('company_switched', {
from_company_id: oldCompanyId,
to_company_id: newCompanyId
})
}
4.3 Anonymous → Authenticated Continuity
PostHog posthog.alias() ile anonymous session'ı authenticated user'a bağlar. Bu signup funnel analizi için kritik:
async function onSignupSuccess(newUser: User) {
const anonymousDistinctId = posthog.get_distinct_id()
posthog.alias(newUser.id, anonymousDistinctId)
posthog.identify(newUser.id, /* properties */)
posthog.capture('user_signed_up', { method: 'email' })
}
Bu olmadan signup öncesi /pricing ziyareti ile signup sonrası ilk VIN sorgusu ayrı user gibi görünür, funnel kırılır.
5. Yeni Event Inventory
Event'ler üç fazda gruplandı: Blocker (Faz 0), High-ROI (Faz 1), Optimization (Faz 2).
5.1 Faz 0 — Pilot Blocker Events (ZORUNLU, 2 gün)
Bu event'ler olmadan BIP pipeline anlamlı insight üretmez.
Identification Audit + Update
| Item | Description |
|---|---|
| Audit | Mevcut identify() çağrı noktaları belirlenir |
| Update | Bölüm 4.1'deki property schema'ya göre identify güncellenir |
| Add | posthog.group('company', ...) çağrısı eklenir |
| Add | posthog.alias() signup flow'a eklenir |
| Add | posthog.reset() logout'a eklenir (yoksa) |
Parts/Compatibility Events (5 adet, en kritik)
Sase.tr'nin core value layer'ı, mevcut tek event (oem_code_copied) yetersiz.
// Kullanıcı parts panel'i ilk gördüğünde
posthog.capture('parts_panel_viewed', {
vehicle_id: string,
parts_count: number,
load_time_ms: number,
source_provider: 'PL24' | 'PCAT' | 'RMEX' | 'TecDoc' | 'multi',
})
// Filter uygulandığında
posthog.capture('parts_filter_applied', {
filter_type: 'category' | 'brand' | 'oem_code' | 'price_range' | 'search',
filter_value: string, // sanitize edilmiş (uzun text varsa truncate)
results_count_before: number,
results_count_after: number,
})
// Compatibility check başlatıldığında
posthog.capture('compatibility_check_initiated', {
part_id: string,
target_vehicle_id: string,
source_vehicle_id: string,
})
// Compatibility check tamamlandığında
posthog.capture('compatibility_check_completed', {
part_id: string,
target_vehicle_id: string,
result: 'compatible' | 'incompatible' | 'unknown' | 'partial',
confidence: number, // 0-1
duration_ms: number,
})
// Parts export başlatıldığında
posthog.capture('parts_export_initiated', {
format: 'csv' | 'xlsx' | 'pdf',
row_count: number,
columns_selected: number,
vehicle_id: string,
has_filters_applied: boolean,
})
Data Privacy Audit
| Item | Effort | Description |
|---|---|---|
| VIN input fields | 1 saat | Tüm VIN input'larına data-private="true" |
| Email display | 1 saat | User email render edilen tüm yerlere data-private |
| Customer data | 2 saat | B2B customer info render'ları |
| API key display | 30 dk | Generated API key gösterimi |
posthog-js config update |
1 saat | maskAllInputs, maskTextSelector ayarları (BIP PRD Bölüm 15.1) |
5.2 Faz 1 — High-ROI Events (Phase 6a paralelinde, 5-7 gün)
VIN Decode Performance Context (mevcut events expansion)
Bölüm 7.1'de detaylı.
Provider Events (4 adet)
Multi-provider arsenal'inin görünür olması için:
posthog.capture('provider_response_received', {
vehicle_id: string,
provider: 'PL24' | 'PCAT' | 'RMEX' | 'TecDoc',
response_time_ms: number,
status: 'success' | 'timeout' | 'error' | 'partial',
data_completeness: number, // 0-1, kaç beklenen field geldi
cached: boolean,
})
posthog.capture('provider_fallback_triggered', {
vehicle_id: string,
from_provider: string,
to_provider: string,
reason: 'timeout' | 'error' | 'incomplete_data' | 'manual',
auto: boolean, // otomatik fallback mi manuel mi
attempt_number: number, // kaçıncı denemede fallback
})
posthog.capture('provider_data_quality_flag', {
vehicle_id: string,
provider: string,
quality_score: number, // 0-100
missing_fields: string[], // ["weight", "year_range"] gibi
warnings: string[],
})
posthog.capture('multi_provider_search_initiated', {
vehicle_id: string,
providers_queried: string[],
parallel: boolean, // parallel mi sequential mi
})
Search Behavior Events (5 adet)
VIN submit öncesi friction'ı yakalamak için:
posthog.capture('search_input_focused', {
field: 'vin' | 'plate' | 'brand_model',
empty: boolean,
})
posthog.capture('search_input_validation_failed', {
field: 'vin' | 'plate',
error_type: 'invalid_format' | 'too_short' | 'too_long' | 'invalid_chars' | 'check_digit_fail',
input_length: number, // VIN'in kendisi DEĞİL, sadece uzunluk
})
posthog.capture('search_paste_detected', {
field: string,
length: number,
source_hint?: 'multiline' | 'with_spaces' | 'clean',
})
posthog.capture('search_history_opened', {
history_count: number,
})
posthog.capture('search_history_item_selected', {
age_days: number, // ne kadar eski bir history item
position: number, // listede kaçıncı
})
Feature Discovery Events (5 adet)
posthog.capture('feature_discovered', {
feature_name: string, // "api_keys", "webhooks", "csv_export"
days_since_signup: number,
discovery_source: 'menu' | 'dashboard' | 'docs' | 'tutorial' | 'modal',
})
posthog.capture('help_clicked', {
context: string, // route path
topic?: string,
})
posthog.capture('docs_link_clicked', {
destination_url: string, // path only, not query
context: string, // route
})
posthog.capture('tutorial_started', {
tutorial_id: string,
trigger: 'auto' | 'manual',
})
posthog.capture('tutorial_step_completed', {
tutorial_id: string,
step_index: number,
step_name: string,
})
posthog.capture('tutorial_abandoned', {
tutorial_id: string,
last_step_index: number,
duration_ms: number,
})
API Key / Webhook Events (4 adet, integration users için kritik)
posthog.capture('api_key_created', {
tier: string,
scope: string[],
expiration_days: number | null,
})
posthog.capture('api_key_revoked', {
age_days: number,
reason?: 'rotation' | 'compromise' | 'unused' | 'manual',
})
posthog.capture('webhook_configured', {
event_types: string[],
endpoint_domain: string, // full URL değil, sadece domain
})
posthog.capture('webhook_delivery_failed', {
webhook_id: string,
event_type: string,
failure_reason: 'timeout' | 'http_error' | 'ssl_error' | 'auth',
retry_count: number,
})
5.3 Faz 2 — Optimization Events (Phase 6c sonrası, fırsatçı)
Bu event'ler pilot'tan insight'lar geldiğinde eklenir, proaktif değil reaktif. LLM "şu event olsaydı daha iyi analiz ederdim" demeye başladığında listeye eklenir.
İlk kestirim listesi:
// Feature interaction patterns
posthog.capture('keyboard_shortcut_used', { shortcut, context })
posthog.capture('bulk_action_initiated', { action, item_count })
posthog.capture('settings_changed', { setting_key, value_changed: true })
// Engagement signals
posthog.capture('page_visibility_changed', { hidden: boolean, time_on_page_ms })
posthog.capture('long_idle_detected', { idle_ms })
// Discovery & SEO
posthog.capture('shared_link_opened', { share_source: string })
posthog.capture('search_engine_referral', { engine, query? })
6. Event Schema Standardı
6.1 Naming Convention
| Pattern | Kullanım | Örnek |
|---|---|---|
<noun>_<verb_past> |
Action / lifecycle event | plan_selected, vin_decode_initiated |
<noun>_<state> |
State change event | subscription_paused, trial_active |
<noun>_<process>_<lifecycle> |
Multi-step process | payment_initiated, payment_completed, payment_failed |
<noun>_<ui_action> |
Passive UI interaction | banner_viewed, modal_dismissed, cta_clicked |
Lifecycle Verb Standardı
Process event'leri için tutarlı lifecycle verbleri:
| Lifecycle | Anlamı |
|---|---|
_initiated |
İşlem başlatıldı, henüz tamamlanmadı |
_started |
_initiated ile aynı; tercih: _initiated (network/API context'inde) veya _started (user time context'inde) |
_succeeded |
Backend confirmation ile başarılı |
_completed |
Multi-step process'in son adımı (success ima eder) |
_failed |
Hata ile sonlandı |
_cancelled |
Kullanıcı tarafından iptal edildi |
_abandoned |
Kullanıcı sessizce ayrıldı (active cancel değil) |
_dismissed |
UI element manuel kapatıldı |
6.2 Property Naming
snake_case kullan (PostHog convention)
boolean property'ler `is_`, `has_`, `was_` prefix'i ile başlamaz; doğal okunur
zamanları ISO 8601 string olarak gönder (`_at` suffix'i)
süreleri `_ms` veya `_seconds` suffix'i ile gönder
count'lar `_count` suffix'i
liste property'leri çoğul (`providers_queried`)
6.3 Required vs Optional Properties
Her event en az şu implicit property'lerle gelir (PostHog otomatik ekler):
$lib(posthog-js vs posthog-node)$browser,$os,$device_type$current_url,$pathnamedistinct_id,$session_idtimestamp
Bizim event-specific property'lerimiz:
- Required property'ler her event'te zorunlu, eksikse capture silinmez (gönderilmemiş gibi davran)
- Optional property'ler bilinmiyorsa omit edilir (null göndermek yerine)
6.4 PII / Sensitive Data Rules
| Veri | İzin | Saklama Şekli |
|---|---|---|
| VIN | ✅ Property olabilir | Property'de full VIN; rrweb recording'de masked |
| ❌ Full email yok | email_domain property'si veya hash |
|
| Telefon | ❌ Full yok | Sadece operator code 5XX |
| TC Kimlik | ❌ Asla | Hiçbir event'te kullanılmaz |
| Plaka | ⚠️ Conditional | Property'de masked (34***123) |
| Auth token / API key | ❌ Asla | Hiçbir event'te kullanılmaz |
| Kredi kartı | ❌ Asla | Hiçbir event'te kullanılmaz |
| Password | ❌ Asla | Hiçbir event'te kullanılmaz |
| IP address | ✅ PostHog otomatik | Geo'ya çevrilir, raw IP saklanmaz |
| User Agent | ✅ PostHog otomatik | |
| URL Path | ✅ | Query string KVKK risk taşırsa filtrele |
| Query string | ⚠️ Conditional | ?vin=XXX gibi PII içerebilir, filtrele |
Önemli: PostHog SDK config'inde property_denylist ile bazı property'ler otomatik silinir:
posthog.init(KEY, {
property_denylist: ['$ip', 'email', 'phone', 'tc_kimlik', 'password', 'token'],
})
VIN property olarak event'te kalabilir çünkü ürünün core'u, ama rrweb recording'inde mask edilir.
6.5 Property Value Length Limits
PostHog property limit'i 8KB. Pratikte:
- String property < 500 karakter (long text varsa truncate +
_truncated: trueflag) - Array property < 50 element
- Nested object property mümkün ama düz tutmak tercih edilir (PostHog filter UX için)
6.6 Versioning
Event schema breaking change yapılırsa rename edilir, asla aynı isimle property değiştirmez:
v1: payment_succeeded { amount, currency }
v2: payment_succeeded_v2 { amount_cents, currency, provider }
1 ay paralel kalır, sonra v1 deprecated. PostHog'da v1 event'leri kapanır (kayıt edilmez), dashboard v2'ye migrate edilir.
7. Mevcut Event İyileştirmeleri
7.1 vin_decode_* Family Expansion
| Mevcut | Yeni |
|---|---|
vin_decoded { vin } |
vin_decode_initiated { vin, vin_brand?, query_source: 'manual' | 'history' | 'paste' | 'qr', has_provider_preference: boolean } |
vin_decode_success { vin, vehicle_id, source? } |
vin_decode_succeeded { vin, vehicle_id, provider, response_time_ms, cache_hit: boolean, ml_decoder_used: boolean, ml_confidence?: number, retry_count: number, partial_result: boolean, fallback_used: boolean } |
vin_decode_error { vin, error, source? } |
vin_decode_failed { vin, error_code, error_message, provider_attempted, response_time_ms, retry_count, will_auto_retry: boolean } |
vin_decode_candidates (no props) |
vin_decode_candidates_shown { vin, candidates_count, top_confidence: number } |
vin_decode_candidate_selected (no props) |
vin_decode_candidate_selected { vin, selected_index: number, selected_confidence: number, time_to_select_ms: number } |
Migration: Bölüm 11'deki rename stratejisi (paralel kalma → deprecate).
7.2 Subscription Funnel Granularity
| Mevcut | Yeni |
|---|---|
plan_selected { plan } |
plan_selected { plan, period, price_usd, position_in_grid: number, source: 'pricing_page' | 'modal' | 'upgrade_prompt' } |
checkout_started { plan, period } |
checkout_started { plan, period, coming_from, plan_comparison_viewed: boolean, time_on_pricing_page_seconds: number, trial_available: boolean } |
trial_started |
trial_started { plan_offered, source: 'signup' | 'pricing_page' | 'modal', trial_days_granted: number } |
subscription_cancelled |
subscription_cancelled { plan, cancellation_reason: 'too_expensive' | 'not_using_enough' | 'missing_feature' | 'found_alternative' | 'temporary' | 'other', cancellation_reason_text?: string, days_since_signup: number, total_vin_queries_lifetime: number, refund_requested: boolean } |
subscription_resumed |
subscription_resumed { plan, days_away: number, came_back_from_offer: boolean } |
downgrade_offer_shown |
downgrade_offer_shown { from_plan, to_plan, discount_pct: number, offer_type: 'discount' | 'plan_downgrade' | 'pause' } |
downgrade_offer_accepted/declined |
Aynı detail seti |
cancel_save_clicked |
cancel_save_offer_accepted { offer_type, savings_usd } |
7.3 Trial Urgency Banner
| Mevcut | Yeni |
|---|---|
trial_urgency_banner_viewed |
trial_urgency_banner_viewed { days_until_trial_end: number, banner_variant: string, plan: string } |
trial_urgency_banner_cta_clicked |
trial_urgency_banner_cta_clicked { days_until_trial_end: number, cta_destination: string } |
trial_urgency_banner_dismissed |
trial_urgency_banner_dismissed { days_until_trial_end: number, dismiss_method: 'x_button' | 'esc' | 'overlay_click' } |
7.4 Payment Dual-Capture Strategy
Karar: Frontend + backend dual capture korunur, ama event isimleri ayrıştırılır.
| Mevcut | Yeni |
|---|---|
payment_success (frontend) |
payment_success_ui (frontend confirmation görüntülendi) |
payment_success (backend) |
payment_succeeded (server source of truth) |
payment_failed (frontend) |
payment_failed_ui (kullanıcıya gösterilen hata) |
payment_failed (backend) |
payment_failed { failure_code, failure_reason, gateway_response, amount, plan } |
Pipeline kullanımı:
payment_succeeded(backend) conversion truthpayment_success_uiolmadanpayment_succeededvarsa → UI bug sinyali (kullanıcı confirmation görmemiş)payment_failedreason'ları cancellation_reason gibi pattern detection için zengin
7.5 Schema/Parts Layer
| Mevcut | Yeni (Faz 0'da eklenenler — Bölüm 5.1) |
|---|---|
oem_code_copied { oem_code, part_id } |
Aynı + parts_panel_viewed, parts_filter_applied, compatibility_check_*, parts_export_initiated |
7.6 Social Proof Attribution
| Mevcut | Yeni |
|---|---|
social_proof_impression { page, section? } |
+ variant_id?: string (A/B test ready) |
social_proof_engaged { page, section? } |
Aynı |
| (yeni) | social_proof_followed_by_action { impression_id?: string, action: 'signup' | 'plan_selected' | 'checkout_started', time_to_action_seconds: number } |
Bu üçüncü event social proof'un gerçek attribution'ını ölçer.
7.7 User Lifecycle
| Mevcut | Yeni |
|---|---|
user_signed_up { method } |
user_signed_up { method, referrer?: string, utm_source?: string, utm_campaign?: string, came_from_pricing: boolean } |
user_logged_in { method } |
user_logged_in { method, was_remember_me: boolean, days_since_last_login: number } |
user_logged_out { reason? } |
Aynı |
8. Data Privacy ve KVKK Uyumu
8.1 data-private Attribute Audit
PostHog SDK config'inde maskTextSelector: '[data-private]' ayarlandığında rrweb recording bu element'leri mask'ler. Tüm sensitive content render'ları işaretlenmeli.
Audit Checklist (Faz 0'da yapılacak)
| Component / Sayfa | Sensitive Content | Action |
|---|---|---|
search.tsx VIN input |
VIN string | data-private attribute |
index.tsx landing VIN demo |
Demo VIN (gerçek değil) | Skip |
parts-panel.tsx |
Müşteri spec'i, fiyat (eğer var) | data-private |
settings-content.tsx |
User email, phone | data-private |
dashboard.tsx user header |
User email render | data-private |
payment-content.tsx |
Card form, amount? | Card → data-private, amount OK |
| API keys view | Generated key string | data-private="strict" (asla rec yapılmasın) |
| Webhook secret view | Webhook secret | data-private="strict" |
_auth/register.tsx, _auth/login.tsx |
Password input | PostHog SDK maskInputOptions.password: true zaten korur, ama yine data-private |
8.2 PostHog SDK Configuration
apps/web/src/lib/posthog/init.ts (veya equivalent) güncellenir:
import posthog from 'posthog-js'
export function initPostHog() {
posthog.init(import.meta.env.VITE_POSTHOG_KEY, {
api_host: 'https://eu.i.posthog.com', // EU region zorunlu (KVKK)
// Session recording config
session_recording: {
maskAllInputs: true,
maskInputOptions: {
password: true,
email: false, // email property zaten denylist'te
tel: true,
number: false,
},
maskTextSelector: '[data-private], .sensitive',
blockSelector: '[data-private="strict"], [data-block]',
blockClass: 'rrweb-block',
ignoreClass: 'rrweb-ignore',
recordCrossOriginIframes: false,
recordNetworkPayloads: false, // payload yok, sadece metadata
recordHeaders: false, // auth header asla
recordBody: false, // request body asla
},
// PII denylist
property_denylist: [
'$ip', // PostHog handle'lar geo'ya
'email', 'phone', 'tc_kimlik',
'password', 'token', 'api_key',
'credit_card', 'cvv',
],
// Defaults
capture_pageview: true,
capture_pageleave: true,
autocapture: {
capture_copied_text: false, // VIN copy event ayrı capture'da
},
persistence: 'localStorage',
cross_subdomain_cookie: true,
secure_cookie: true,
// Performance
request_batching: true,
// KVKK
opt_out_capturing_by_default: false, // consent flow Sase.tr login öncesi
})
}
8.3 Backend SDK (posthog-node) Configuration
// apps/api/src/lib/posthog.ts
import { PostHog } from 'posthog-node'
export const posthogServer = new PostHog(
process.env.POSTHOG_KEY!,
{
host: 'https://eu.i.posthog.com',
flushAt: 20, // 20 event'te bir flush
flushInterval: 10000, // 10 saniyede bir flush
}
)
// Wrapper: PII filtering at the source
export function captureFromBackend(params: {
distinctId: string,
event: string,
properties: Record<string, any>,
groups?: Record<string, string>,
}) {
const sanitized = sanitizeProperties(params.properties)
posthogServer.capture({
distinctId: params.distinctId,
event: params.event,
properties: { ...sanitized, $lib: 'posthog-node' },
groups: params.groups,
})
}
function sanitizeProperties(props: Record<string, any>): Record<string, any> {
const denylist = ['email', 'phone', 'password', 'token', 'api_key', 'credit_card']
return Object.fromEntries(
Object.entries(props).filter(([k]) => !denylist.includes(k))
)
}
8.4 KVKK Aydınlatma Metni Güncelleme
Sase.tr KVKK aydınlatma metnine eklenecek madde (Bölüm 6.x):
Ürün Analitiği ve Davranış Analizi
Sase.tr kullanım deneyiminizi iyileştirmek için PostHog (PostHog Inc., Hollanda) tarafından sağlanan ürün analitiği hizmetini kullanır. Bu hizmet kapsamında:
- Hangi sayfaları ziyaret ettiğiniz, hangi özellikleri kullandığınız
- VIN sorgu, parça arama gibi platforma özgü etkileşimleriniz
- Cihaz ve tarayıcı bilgileriniz
- Anonimleştirilmiş davranış kayıtları (session recording)
verileri işlenir. Bu veriler AB Birliği'nde (Frankfurt) saklanır.
İşlenen verilerden anonimleştirilmiş özetler, ürün geliştirme amacıyla yapay zeka destekli analiz için DeepSeek (DeepSeek Inc., Çin Halk Cumhuriyeti) üzerinden işlenebilir. Bu işlemden önce kişisel veri içeriği maskelenir.
Bu işleme faaliyetinin yasal dayanağı KVKK Madde 5/2-f kapsamında meşru menfaattir.
Hukuk danışmanı ile kontrol edilmeli.
8.5 Consent Flow (Opsiyonel, Phase 2'de)
Şu an default opt_out_capturing_by_default: false. Eğer Sase.tr EU müşterileri (GDPR) çekmek istiyorsa cookie banner consent flow eklenmeli:
// Kullanıcı consent verince
posthog.opt_in_capturing()
// Reddederse
posthog.opt_out_capturing()
Bu pilot scope'unda değil, Türkiye merkezli kullanıcı varsayımı.
9. Implementation Plan
Faz 0 — Pilot Blocker (2 gün, mecburi)
Gün 1: Identification + Privacy
Sabah (4 saat)
- Mevcut
posthog.identify()çağrıları audit edilir - User properties schema implement edilir (Bölüm 4.1)
posthog.group('company')çağrısı eklenirposthog.alias()signup flow'a eklenirposthog.reset()logout'a eklenir
Öğlen (4 saat)
data-privateaudit (Bölüm 8.1 checklist)- PostHog SDK config update (Bölüm 8.2 + 8.3)
- EU region confirmation (
https://eu.i.posthog.com) - Local test: identify + group + recording mask
Gün 2: Parts Layer Events
Sabah (4 saat)
parts_panel_viewedevent implementationparts_filter_appliedevent implementationcompatibility_check_initiated+compatibility_check_completed
Öğlen (4 saat)
parts_export_initiatedevent- Existing
oem_code_copiedproperty zenginleştirme - Documentation:
docs/posthog-events.mdilk versiyonu - PR review + merge
Çıkış kriteri: Faz 0 PR merge edildiğinde:
- Tüm authenticated session'lar
posthog.identify()veposthog.group()çağırıyor - Parts layer'ı görünür
- rrweb recording'lerde VIN/email mask'lenmiş
- KVKK aydınlatma metni güncel
Faz 1 — High-ROI Events (5-7 gün, BIP Phase 6a paralelinde)
| Gün | Konu |
|---|---|
| 1 | VIN decode performance expansion (Bölüm 7.1) |
| 2 | Provider events (4 adet, Bölüm 5.2) |
| 3 | Search behavior events (5 adet, Bölüm 5.2) |
| 4 | Feature discovery events (5 adet, Bölüm 5.2) |
| 5 | API key/webhook events + Subscription funnel granularity (Bölüm 7.2) |
| 6 | Payment dual-capture rename (Bölüm 7.4) + Trial banner expansion |
| 7 | QA, documentation update, deploy |
Strateji: Tek bir feature branch (feature/posthog-event-expansion-phase1), günlük commit'ler, sonunda tek büyük PR. Production'a deploy edilir, 1 hafta baseline ölçülür, sonra BIP pipeline Faz 6b'ye geçilir.
Faz 2 — Optimization (Phase 6c sonrası, ad-hoc)
BIP'ten gelen meta-insight'lara göre event ekleme. Plan değil pattern.
Effort Özeti
| Faz | Süre | Effort | Çıktı |
|---|---|---|---|
| Faz 0 | 2 gün | Solo founder full-time | Pilot blocker'lar |
| Faz 1 | 5-7 gün | Solo founder full-time | 30+ yeni event, mevcut iyileştirme |
| Faz 2 | Sürekli | 1-2 saat/event | Insight-driven additions |
| Toplam (Faz 0+1) | ~2 hafta | Pilot için event hazır |
10. Testing Strategy
10.1 Local Development
// posthog dev environment
if (import.meta.env.DEV) {
posthog.init(import.meta.env.VITE_POSTHOG_KEY_DEV, {
api_host: 'https://eu.i.posthog.com',
debug: true, // console'da event log
disable_session_recording: true, // dev'de recording yok
})
}
Browser console'da event flow gözle takip edilir.
10.2 Event Payload Validation
Type-safe wrapper ile yanlış property kullanımı compile-time'da yakalanır:
// src/lib/posthog/events.ts
import posthog from 'posthog-js'
type PostHogEvent =
| { name: 'vin_decode_initiated'; props: VinDecodeInitiatedProps }
| { name: 'vin_decode_succeeded'; props: VinDecodeSucceededProps }
| { name: 'parts_panel_viewed'; props: PartsPanelViewedProps }
// ... 50+ event type
export function capture<E extends PostHogEvent>(event: E) {
posthog.capture(event.name, event.props)
}
// Kullanım: compile-time safety
capture({
name: 'vin_decode_initiated',
props: { vin: '...', query_source: 'manual' } // missing prop → TS error
})
10.3 Production Validation
PR merge sonrası 24 saat içinde:
- PostHog dashboard'unda her yeni event tipi en az 1 kez görünüyor
- Event property'leri schema'ya uyuyor
- No errors in browser console (PostHog SDK errors)
- rrweb recordings'te masked content masked
- User properties identify sonrası set ediliyor (PostHog person page'de görünüyor)
10.4 Regression Test (Mevcut Funnel'ları Bozmama)
Faz 1'de mevcut event'leri rename ederken (vin_decoded → vin_decode_initiated gibi):
- Eski event 1 ay daha capture edilir (parallel)
- PostHog'da mevcut funnel/dashboard duplicate edilir (v2 versiyonları)
- v2'ler yeni event isimleriyle kurulur
- 1 ay sonra v1 capture durur, v1 dashboard'lar arşivlenir
Bu adımlar Bölüm 11'de detaylı.
11. Migration ve Backward Compatibility
11.1 Rename Stratejisi
Event rename gerektiren durumlar:
vin_decoded→vin_decode_initiatedvin_decode_success→vin_decode_succeededvin_decode_error→vin_decode_failedpayment_success(FE) →payment_success_uipayment_success(BE) →payment_succeededpayment_failed(BE) → daha detaylı properties
Migration Steps
Adım 1: Dual-emit phase (1 ay)
// Both old and new emitted
posthog.capture('vin_decoded', { vin }) // legacy
posthog.capture('vin_decode_initiated', {
vin, vin_brand, query_source: 'manual', has_provider_preference: false
}) // new
Adım 2: PostHog dashboard migration (1-3 gün, 1 ay sürecin başında)
- Mevcut dashboard'ları duplicate et, "_v2" ekle
- v2'leri yeni event isimleri ile re-create et
- 1 hafta paralel izle, data tutuyor mu kontrol et
- Eski dashboard'lar
[DEPRECATED]ile prefix'lenir
Adım 3: Funnel migration
- Mevcut funnels duplicate et
- v2 funnels yeni event isimleriyle kurulur
- Alert'leri v2'ye taşı
Adım 4: Legacy capture kapanır (1 ay sonra)
// Sadece new event
posthog.capture('vin_decode_initiated', { ... })
Eski dashboard'lar arşivlenir (silinmez, history için).
11.2 Payment Event Migration (Özel Durum)
Payment event'leri production critical (finansal alert'ler buna bağlı olabilir). Daha dikkatli:
Adım 1: Backend'de payment_succeeded yeni event olarak başlatılır. payment_success capture'ı korunur.
Adım 2: PostHog'da yeni alert kurulur (payment_succeeded failure rate).
Adım 3: 2 hafta paralel izle. Eski ve yeni event count'u %95+ matching olmalı.
Adım 4: Eski payment_success (backend) deprecate edilir, sadece frontend payment_success_ui kalır.
11.3 Property Addition Backward Compat
Yeni property eklemek backward compatible:
- Eski filter'lar yeni property'siz çalışmaya devam eder
- Yeni filter'lar property eksik event'leri sayar veya saymaz (filter behavior)
Bu adım için migration gerekmez, sadece event implementation.
11.4 Mevcut Dashboard / Funnel İnvanteri
Migration başlamadan önce mevcut PostHog asset'leri envanterlenmeli:
| Asset Type | Identify | Action |
|---|---|---|
| Funnels | List all | v2 duplicate plan |
| Insights / Trends | List all using affected events | v2 duplicate plan |
| Dashboards | List all | Component update plan |
| Alerts | List all | Recreate on v2 |
| Cohorts | List all using affected events | Verify still valid |
Bu envanter Faz 1 başlamadan önce 1 saatlik audit ile çıkarılır.
12. PostHog Dashboard Setup
Yeni event'lerin maximum değer üretmesi için PostHog UI'da kurulacak dashboard ve funnel'lar:
12.1 Critical Funnels
Activation Funnel
user_signed_up
→ user_logged_in (within 1 day)
→ vin_decode_initiated
→ vin_decode_succeeded
→ parts_panel_viewed
→ oem_code_copied OR parts_export_initiated
Conversion Funnel
$pageview (/pricing)
→ plan_selected
→ checkout_started
→ payment_initiated
→ payment_succeeded
Trial → Paid Funnel
trial_started
→ vin_decode_succeeded (during trial)
→ trial_urgency_banner_cta_clicked
→ checkout_started
→ payment_succeeded
Retention Funnel (Weekly)
Week 1: user_signed_up
Week 2: vin_decode_succeeded
Week 3: vin_decode_succeeded
Week 4: vin_decode_succeeded
12.2 Critical Cohorts
| Cohort | Definition | Use |
|---|---|---|
power_users |
Last 30d: >100 vin_decode_succeeded | BIP segment context |
at_risk |
Last 14d: 0 vin_decode_initiated AND plan_tier != 'free' | Churn prevention |
new_users_week1 |
signup_date within last 7 days | Onboarding analysis |
provider_heavy_pl24 |
Last 30d: >70% provider = 'PL24' | Provider reliability impact |
multi_brand_users |
distinct vin_brand count >= 3 in last 30d | Feature usage analysis |
api_integrators |
api_key_created event exists | Power users, different needs |
failed_payment_recovery |
payment_failed exists, but no payment_succeeded within 7d | Recovery target |
12.3 Insights (Trends)
| Insight | Description |
|---|---|
| Daily VIN queries by provider | Provider load distribution |
| VIN decode success rate by provider | Quality metric |
| Average response time by provider | Performance metric |
| Onboarding completion rate (weekly) | Activation health |
| Trial → paid conversion (monthly) | Revenue health |
| Feature discovery rate by feature | Discoverability gap |
| Compatibility check accuracy distribution | Product quality |
12.4 Alerts
| Alert | Trigger | Notification |
|---|---|---|
| Payment success rate drops | < 90% in last hour | Telegram immediate |
| VIN decode failure spike | > 3x baseline in 15 min | Telegram |
| Provider PL24 timeout > 20% | Continuous 30 min | |
| Daily active users drop | < 80% of 7-day avg | Daily digest |
| New signup → first VIN query | < 50% in 24h | Weekly review |
Bu alert'ler PostHog'un kendi alert sisteminden, BIP pipeline'a webhook olarak da gönderilebilir (BIP PRD Bölüm "Pattern-Level Otomatik Insight Tetikleme").
13. Documentation Standardı
13.1 Single Source of Truth
docs/posthog-events.md — tüm event taxonomy'sinin tek kaynağı:
# Sase.tr PostHog Event Taxonomy
> Last updated: [date]
> Version: 2.0
## Conventions
[Bölüm 6'dan kopyala]
## Event Catalog
### Auth & Lifecycle
#### user_signed_up
- **Description:** User completed signup
- **Trigger:** After successful account creation
- **Source:** `apps/web/src/routes/_auth/register.tsx`
- **Properties:**
- `method` (required): "email" | "google"
- `referrer` (optional): string
- `utm_source` (optional): string
- `utm_campaign` (optional): string
- `came_from_pricing` (required): boolean
- **Example:**
```ts
capture({ name: 'user_signed_up', props: { method: 'email', came_from_pricing: true } })
user_logged_in
... [her event için aynı format]
### 13.2 Inline Code Documentation
Her capture noktası açıklayıcı yorumla:
```typescript
/**
* VIN decode başlatıldığında çağrılır.
* Pipeline kullanımı: vin_decode_initiated → vin_decode_succeeded/failed funnel.
*
* @see docs/posthog-events.md#vin_decode_initiated
*/
capture({
name: 'vin_decode_initiated',
props: { vin, query_source: 'manual', has_provider_preference: false }
})
13.3 Schema Versioning
docs/posthog-events.md header'ında version. Breaking change'lerde version bump:
- v1.x: Mevcut taxonomy
- v2.0: Faz 1 sonrası
13.4 Onboarding Yeni Developer
Yeni dev (gelecekte) için checklist:
docs/posthog-events.mdoku- Type-safe wrapper kullan (
src/lib/posthog/events.ts) - Yeni event ekleyince:
- Type tanımı
events.ts'e ekle docs/posthog-events.mdgüncelle- PR description'da event listele
- Type tanımı
14. Riskler
| Risk | Olasılık | Etki | Azaltma |
|---|---|---|---|
| Mevcut funnel/dashboard'lar rename ile kırılır | Yüksek | Orta | Dual-emit phase + v2 dashboard parallel; Bölüm 11 |
| Property denylist eksik kalır, PII PostHog'a gider | Orta | Yüksek | Bölüm 6.4 + 8.2 review; Faz 0'da audit |
| Identify çağrısı login dışı yerde unutulur | Orta | Orta | Type-safe wrapper + lint rule |
| Group analytics company switch'te update edilmez | Yüksek | Orta | Multi-tenant flow test; Bölüm 4.2 |
| Yeni event'ler PostHog cost'unu artırır | Düşük | Düşük | Free tier 1M event/ay yeterli pilot için |
| Payment dual-capture geçişinde alert'ler kaybolur | Orta | Yüksek | Alert recreate Bölüm 12.4 + audit |
| rrweb mask config bozulur, VIN recording'lerde görünür | Düşük | Yüksek | Faz 0 test + production smoke test |
| Backend event'leri server crash'te kaybolur | Düşük | Düşük | posthog-node flush on shutdown; SIGTERM handler |
| Type-safe wrapper adoption düşük olur | Orta | Düşük | Lint rule: direct posthog.capture() block edilir |
| Faz 1 implementation 7 günde tamamlanmaz | Orta | Orta | Her event independent ship edilebilir; BIP Phase 6a paralel |
15. Karar Logu
| Tarih | Karar | Gerekçe |
|---|---|---|
| 2026-05-13 | PostHog Cloud EU region kullanılacak | KVKK uyumu; AWS Frankfurt veri konumu |
| 2026-05-13 | Group analytics (posthog.group('company')) zorunlu |
B2B insight kalitesi için belkemiği |
| 2026-05-13 | Email asla full olarak property gönderilmez, sadece domain | KVKK + property denylist enforcement |
| 2026-05-13 | VIN property'de full kalır, rrweb recording'de masked | Ürün analitiği için gerekli; PII risk recording'de değil property'de düşük |
| 2026-05-13 | Frontend + backend payment dual-capture korunur, rename edilir | Reliability + UI bug detection sinyali |
| 2026-05-13 | Event naming: <noun>_<verb_past> convention |
Tutarlı arama, PostHog UI ergonomics |
| 2026-05-13 | Property naming: snake_case | PostHog convention |
| 2026-05-13 | Type-safe wrapper zorunlu (src/lib/posthog/events.ts) |
Yanlış event/property compile-time yakalama |
| 2026-05-13 | docs/posthog-events.md single source of truth |
Drift önleme |
| 2026-05-13 | Faz 0 (2 gün) BIP Phase 6a önkoşul | Pilot insight kalitesi için zorunlu |
| 2026-05-13 | Faz 1 (5-7 gün) BIP Phase 6a paralel | Time-to-pilot kısalır |
| 2026-05-13 | Faz 2 ad-hoc, insight-driven | Premature optimization önleme |
| 2026-05-13 | Rename'ler 1 ay dual-emit phase ile yapılır | Production funnel'ları bozmamak |
Doküman Sonu