Analytics: Privacy-Compliant Event Tracking
Whistl tracks usage analytics to improve the product while respecting user privacy. This comprehensive guide explains GA4 implementation, event taxonomy, data minimization, consent management, and how Whistl complies with GDPR, CCPA, and privacy best practices.
Why Privacy-First Analytics?
Financial apps handle sensitive data requiring extra care:
- User trust: Privacy is essential for financial apps
- Regulatory compliance: GDPR, CCPA require consent
- Data minimization: Collect only what's necessary
- Transparency: Users should know what's tracked
- Control: Users can opt out anytime
Whistl's analytics respect privacy while providing actionable insights.
Analytics Architecture
Whistl uses Google Analytics 4 with privacy enhancements:
Data Flow
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │ │ Consent │ │ GA4 │ │ BigQuery │
│ App │───▶│ Manager │───▶│ Servers │───▶│ (Optional)│
│ │ │ │ │ │ │ │
└─────────────┘ └──────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
│ Event │ Check │ Process │
│ - Name │ Consent │ - Anonymize │
│ - Properties │ Status │ - Aggregate │
│ - User ID (hash) │ │ - Retain │
│ │ │ │
▼ ▼ ▼ ▼
Track Event Respect Choice Privacy-First Secure Storage
(Opt-in/out) Analytics
Event Taxonomy
Whistl uses a structured event naming convention:
Event Categories
| Category | Prefix | Examples |
|---|---|---|
| App Lifecycle | app_ | app_open, app_close, app_update |
| Authentication | auth_ | auth_login, auth_logout, auth_biometric |
| Navigation | nav_ | nav_screen_view, nav_back, nav_tab_select |
| Intervention | intervention_ | intervention_shown, intervention_accepted |
| Blocking | block_ | block_triggered, block_bypass_attempt |
| Goals | goal_ | goal_created, goal_updated, goal_completed |
| Settings | settings_ | settings_changed, notifications_enabled |
Event Properties
// Good: Privacy-respecting properties
{
"event": "intervention_shown",
"properties": {
"intervention_type": "venue_proximity",
"risk_level": "high",
"step_number": 3,
"time_of_day": "evening",
"day_of_week": "friday"
}
}
// Bad: Contains sensitive data (DON'T DO THIS)
{
"event": "intervention_shown",
"properties": {
"user_balance": 1234.56, // Financial data!
"location_lat": -37.8225, // Precise location!
"merchant_name": "Crown Casino" // Specific venue!
}
}
GA4 Implementation
Google Analytics 4 configured for privacy:
iOS Implementation
import GoogleAnalytics
class AnalyticsManager {
private var analytics: Analytics?
private var consentGiven = false
func initialize() {
analytics = Analytics.analytics()
// Privacy settings
analytics?.setAnalyticsCollectionEnabled(false) // Default off
// Disable advertising features
analytics?.defaultEventParameters = [
"allow_ad_personalization_signals": false
]
// Configure data retention (2 months minimum)
analytics?.setDataRetentionDays(60)
}
func setConsent(_ granted: Bool) {
consentGiven = granted
analytics?.setAnalyticsCollectionEnabled(granted)
// Update consent mode
let consentSettings = GAConsentSettings()
consentSettings.analyticsStorage = granted ? .granted : .denied
consentSettings.adStorage = .denied // Never enable ads
GATagManager.instance.setConsent(consentSettings)
}
func trackEvent(name: String, properties: [String: Any]?) {
guard consentGiven else { return }
// Strip any sensitive data
let sanitizedProperties = sanitize(properties)
analytics?.logEvent(name, parameters: sanitizedProperties)
}
private func sanitize(_ properties: [String: Any]?) -> [String: Any]? {
// Remove any properties that might contain PII
let sensitiveKeys = ["email", "phone", "address", "balance",
"account_number", "latitude", "longitude"]
return properties?.filter { key, value in
!sensitiveKeys.contains(where: key.lowercased().contains)
}
}
}
Android Implementation
import com.google.firebase.analytics.FirebaseAnalytics
class AnalyticsManager(private val context: Context) {
private val analytics: FirebaseAnalytics = FirebaseAnalytics.getInstance(context)
private var consentGiven = false
fun setConsent(granted: Boolean) {
consentGiven = granted
val consent = ConsentMap.builder()
.setConsent(ConsentType.ANALYTICS_STORAGE,
if (granted) ConsentStatus.GRANTED else ConsentStatus.DENIED)
.setConsent(ConsentType.AD_STORAGE, ConsentStatus.DENIED) // Never ads
.build()
ConsentUpdateSdk.getInstance().updateConsent(consent)
analytics.setAnalyticsCollectionEnabled(granted)
}
fun trackEvent(name: String, properties: Bundle?) {
if (!consentGiven) return
val sanitized = sanitize(properties)
analytics.logEvent(name, sanitized)
}
private fun sanitize(bundle: Bundle?): Bundle? {
// Remove sensitive parameters
val sensitiveKeys = listOf("email", "phone", "balance", "location")
return bundle?.let { b ->
Bundle().apply {
b.keySet().forEach { key ->
if (!sensitiveKeys.any { key.lowercase().contains(it) }) {
putAll(b)
}
}
}
}
}
}
User ID Handling
User IDs are hashed before sending to analytics:
ID Hashing
import CryptoKit
class UserIDHasher {
private let salt = "whistl_analytics_salt_v1"
func hash(userId: String) -> String {
// Combine user ID with salt
let data = (userId + salt).data(using: .utf8)!
// SHA-256 hash
let hash = SHA256.hash(data: data)
// Return first 16 characters (64 bits)
return hash.compactMap { String(format: "%02x", $0) }
.joined()
.prefix(16)
.description
}
}
// Result: usr-abc123 → "a1b2c3d4e5f6g7h8"
// Can't reverse engineer original ID from hash
Consent Management
Users control analytics tracking:
Consent Flow
- First launch: Consent dialog shown
- Default: Analytics disabled (opt-in)
- Settings: Can change anytime
- Withdrawal: Data deleted on opt-out
Consent Dialog
struct ConsentDialog: View {
@State private var analyticsConsent = false
@State private var crashReportingConsent = true // Default on for safety
var body: some View {
VStack(spacing: 24) {
Text("Privacy Settings")
.font(.largeTitle)
.fontWeight(.bold)
Text("Help us improve Whistl by sharing anonymous usage data. No personal or financial information is ever shared.")
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
Toggle("Share anonymous analytics", isOn: $analyticsConsent)
Toggle("Send crash reports", isOn: $crashReportingConsent)
HStack {
Button("Continue") {
saveConsent(analytics: analyticsConsent,
crashes: crashReportingConsent)
}
.buttonStyle(.borderedProminent)
}
}
.padding()
}
}
Data Retention
Analytics data has limited retention:
Retention Settings
| Data Type | Retention | Reason |
|---|---|---|
| Event Data | 2 months | Minimum GA4 allows |
| User Properties | 2 months | Tied to event data |
| Aggregated Reports | Indefinite | No PII, statistical only |
| Crash Reports | 90 days | Debugging needs |
Event Examples
Real events tracked by Whistl:
Key Events
// App Open
{
"event": "app_open",
"properties": {
"session_id": "sess_abc123",
"time_of_day": "morning",
"day_of_week": "monday",
"app_version": "1.8.2"
}
}
// Intervention Shown
{
"event": "intervention_shown",
"properties": {
"intervention_type": "spending_shield",
"risk_level": "high",
"step_number": 3,
"variant": "tough_love" // For A/B testing
}
}
// Goal Completed
{
"event": "goal_completed",
"properties": {
"goal_type": "savings",
"days_to_complete": 45,
"goal_id_hash": "g1a2b3c4" // Hashed, not raw ID
}
}
Privacy Compliance
Whistl complies with major privacy regulations:
GDPR Compliance
- Lawful basis: Consent for analytics
- Purpose limitation: Only for product improvement
- Data minimization: Only necessary data collected
- Right to access: Users can request data export
- Right to deletion: Users can request data deletion
- Data portability: Export in machine-readable format
CCPA Compliance
- Right to know: Disclose what's collected
- Right to delete: Delete on request
- Right to opt-out: "Do Not Sell" honored
- No discrimination: Same service regardless of choice
Analytics Dashboard
Team uses privacy-safe analytics for decisions:
Key Metrics
| Metric | Definition | Target |
|---|---|---|
| Daily Active Users | Unique users per day | Growth |
| Session Duration | Average time in app | >5 min |
| Intervention Acceptance | % who engage with intervention | >70% |
| Retention (D30) | % active after 30 days | >60% |
| Goal Completion Rate | % goals completed | >40% |
Conclusion
Whistl's privacy-compliant analytics provide actionable insights while respecting user privacy. Through GA4 with enhanced privacy settings, consent management, data minimization, and limited retention, Whistl improves the product without compromising user trust.
Users control their data—analytics is opt-in, and can be disabled anytime in settings.
Privacy-First Protection
Whistl respects your privacy while continuously improving. Download free and control your analytics preferences.
Download Whistl FreeRelated: GDPR & CCPA Compliance | A/B Testing Infrastructure | Local Storage Encryption