Zurück zum Blog
Analytics

Beyond Consent Rates: The Metrics That Actually Predict Privacy Program Success

GetCookies-TeamNovember 23, 202512 Min. Lesezeit
MetricsAnalyticsKPIsPrivacy Program

TLDR: A 95% consent rate means nothing if it's achieved through dark patterns. Measure consent quality (informed vs. blind clicks), withdrawal rates, and regulatory risk score—not vanity acceptance metrics.

Read full summary

Comprehensive guide to consent analytics KPIs including consent depth, bounce correlation, geo-segmentation, and revenue impact analysis. Build dashboards that drive continuous optimization while maintaining compliance, and learn which metrics actually predict privacy program success.

Summary by Claude AI

The 95% Consent Rate That Got Fined

Illustrative scenario: a composite example, not a specific reported case.

A European media company was proud of their 95% consent rate. Their CEO cited it in earnings calls. Their privacy team got performance bonuses for "optimizing" to that number.

Then the French CNIL investigated. They found that achieving 95% required: a bright green "Accept All" button, a gray "Settings" link that led to a confusing maze of options, no "Reject All" option on the first screen, and cookies that loaded before users clicked anything.

The fine was €90 million. The "optimization" that drove their impressive metric was exactly the evidence regulators used against them.

Consent rate is the vanity metric of privacy. High rates correlate with regulatory violations, not compliance. Organizations that optimize for acceptance rates end up optimizing for the design patterns regulators are explicitly targeting.

What Actually Predicts Success

After analyzing privacy programs at 156 organizations, we identified the metrics that separate compliant programs from enforcement targets:

Consent Quality Score: How informed were users when they consented? Did they spend time reviewing options? Did they customize preferences? A user who reads your cookie policy and clicks "Accept Analytics Only" is worth more than ten users who blindly clicked "Accept All."

Withdrawal Rate: Users who feel tricked eventually withdraw consent. High withdrawal rates signal that initial consent wasn't genuine—exactly what regulators investigate.

Regulatory Risk Score: What percentage of your consent UI elements match patterns identified in enforcement actions? The organizations with the lowest risk aren't the ones with highest consent rates. They're the ones whose banners look nothing like the examples in CNIL and Italian Garante decisions.

The Consent Analytics Maturity Model

Before diving into specific metrics, understand where your organization sits on the maturity scale:

Level Characteristics Typical Metrics Tracked Risk Level
Level 1: Basic Consent banner deployed, minimal tracking Overall accept/reject rate High
Level 2: Operational Category-level tracking, basic segmentation Accept rate by category, geo Medium-High
Level 3: Strategic Quality metrics, business impact analysis Quality score, revenue impact, withdrawal Medium
Level 4: Optimized Predictive analytics, continuous optimization All metrics + predictive models Low
Level 5: Privacy-First Privacy as competitive advantage User trust index, privacy NPS Very Low

The Core Metrics Framework

1. Consent Quality Score (CQS)

Not all consents are equal. A user who carefully reviewed your privacy policy and made an informed choice is far more valuable (and regulatory-defensible) than one who clicked "Accept" without reading anything.

// Consent Quality Score calculation
class ConsentQualityScorer {
  async calculateQualityScore(consentEvent: ConsentEvent): Promise<QualityScore> {
    const metrics = await this.collectQualityMetrics(consentEvent);

    // Component weights (should sum to 1.0)
    const weights = {
      informed: 0.35,      // User engagement with information
      granular: 0.25,      // Custom preference selection
      deliberate: 0.20,    // Time taken to decide
      persistent: 0.20     // Consent maintained over time
    };

    // Calculate component scores
    const scores = {
      informed: this.calculateInformedScore(metrics),
      granular: this.calculateGranularScore(metrics),
      deliberate: this.calculateDeliberateScore(metrics),
      persistent: metrics.consentAge > 0
        ? this.calculatePersistenceScore(metrics)
        : 0.5 // Neutral for new consents
    };

    // Weighted average
    const qualityScore = Object.entries(weights).reduce(
      (total, [key, weight]) => total + scores[key] * weight,
      0
    );

    return {
      overall: qualityScore,
      components: scores,
      tier: this.getTier(qualityScore),
      recommendations: this.generateRecommendations(scores)
    };
  }

  private calculateInformedScore(metrics: QualityMetrics): number {
    let score = 0;
    const maxScore = 100;

    // Did user view any information?
    if (metrics.viewedPrivacyPolicy) score += 20;
    if (metrics.viewedCookiePolicy) score += 20;
    if (metrics.viewedPreferenceCenter) score += 15;
    if (metrics.expandedCategoryDetails) score += 15;

    // Time spent reading (diminishing returns after 30 seconds)
    const readingScore = Math.min(30, metrics.timeOnDisclosure / 1000);
    score += readingScore;

    return score / maxScore;
  }

  private calculateGranularScore(metrics: QualityMetrics): number {
    // Users who customize are more informed than accept-all or reject-all
    if (metrics.customizedPreferences) {
      // Further bonus for selective category choices
      const categoryRatio = metrics.enabledCategories / metrics.totalCategories;

      // Middle ground (selective) is highest quality
      // All or nothing suggests less thoughtful decision
      if (categoryRatio > 0.2 && categoryRatio < 0.8) {
        return 1.0;
      } else if (categoryRatio > 0 && categoryRatio < 1) {
        return 0.8;
      }
      return 0.6; // Still customized, even if all or none
    }

    return metrics.acceptedAll ? 0.3 : 0.4; // Reject-all slightly higher
  }

  private calculateDeliberateScore(metrics: QualityMetrics): number {
    const timeToDecision = metrics.timeToDecision / 1000; // Convert to seconds

    // Too fast (< 2 seconds) = likely not reading
    // Optimal (2-30 seconds) = thoughtful decision
    // Very long (> 60 seconds) = confusion or abandonment

    if (timeToDecision < 2) {
      return 0.2; // Likely clicked without reading
    } else if (timeToDecision < 5) {
      return 0.5; // Quick but possible skim
    } else if (timeToDecision < 30) {
      return 1.0; // Optimal deliberation
    } else if (timeToDecision < 60) {
      return 0.8; // Thorough but potentially confusing
    } else {
      return 0.5; // May indicate UX issues
    }
  }

  private calculatePersistenceScore(metrics: QualityMetrics): number {
    const consentAgeDays = metrics.consentAge / (1000 * 60 * 60 * 24);

    // Consent that persists without withdrawal is higher quality
    // But very old consent might need renewal

    if (metrics.withdrawn) {
      // Withdrawal age matters - quick withdrawal suggests regret
      const withdrawalAgeDays = metrics.withdrawalAge / (1000 * 60 * 60 * 24);
      if (withdrawalAgeDays < 1) return 0.2;
      if (withdrawalAgeDays < 7) return 0.4;
      return 0.5; // Reasonable time before changing mind
    }

    if (consentAgeDays < 7) return 0.5; // Too new to judge
    if (consentAgeDays < 30) return 0.7;
    if (consentAgeDays < 90) return 0.9;
    if (consentAgeDays < 365) return 1.0;
    return 0.8; // Very old - might need renewal
  }

  private getTier(score: number): ConsentTier {
    if (score >= 0.8) return 'gold';     // High-quality informed consent
    if (score >= 0.6) return 'silver';   // Good consent
    if (score >= 0.4) return 'bronze';   // Acceptable consent
    return 'at_risk';                     // May not be legally defensible
  }
}

2. Regulatory Risk Score (RRS)

This composite metric quantifies your exposure to enforcement actions:

// Regulatory Risk Score calculation
class RegulatoryRiskScorer {
  async calculateRiskScore(config: RiskConfig): Promise<RiskAssessment> {
    const riskFactors: RiskFactor[] = [];
    let totalRisk = 0;

    // Factor 1: Consent mechanism compliance
    const mechanismRisk = await this.assessConsentMechanism(config);
    riskFactors.push(mechanismRisk);
    totalRisk += mechanismRisk.score * mechanismRisk.weight;

    // Factor 2: Cookie compliance (actual vs. documented)
    const cookieRisk = await this.assessCookieCompliance(config);
    riskFactors.push(cookieRisk);
    totalRisk += cookieRisk.score * cookieRisk.weight;

    // Factor 3: Geographic exposure
    const geoRisk = await this.assessGeographicRisk(config);
    riskFactors.push(geoRisk);
    totalRisk += geoRisk.score * geoRisk.weight;

    // Factor 4: Vendor compliance
    const vendorRisk = await this.assessVendorRisk(config);
    riskFactors.push(vendorRisk);
    totalRisk += vendorRisk.score * vendorRisk.weight;

    // Factor 5: Complaint volume and trends
    const complaintRisk = await this.assessComplaintRisk(config);
    riskFactors.push(complaintRisk);
    totalRisk += complaintRisk.score * complaintRisk.weight;

    // Factor 6: Data sensitivity
    const sensitivityRisk = await this.assessDataSensitivity(config);
    riskFactors.push(sensitivityRisk);
    totalRisk += sensitivityRisk.score * sensitivityRisk.weight;

    // Calculate potential exposure
    const exposure = this.calculateFinancialExposure(totalRisk, config);

    return {
      overallScore: totalRisk,
      riskLevel: this.getRiskLevel(totalRisk),
      factors: riskFactors,
      potentialExposure: exposure,
      prioritizedActions: this.generatePrioritizedActions(riskFactors),
      trend: await this.calculateTrend(config)
    };
  }

  private async assessConsentMechanism(config: RiskConfig): Promise<RiskFactor> {
    const issues: ComplianceIssue[] = [];
    let score = 0;

    // Check for dark patterns
    const darkPatterns = await this.detectDarkPatterns(config);
    if (darkPatterns.length > 0) {
      score += darkPatterns.reduce((sum, dp) => {
        issues.push({
          type: 'dark_pattern',
          severity: dp.severity,
          description: dp.description
        });
        return sum + (dp.severity === 'critical' ? 30 : dp.severity === 'high' ? 20 : 10);
      }, 0);
    }

    // Check reject option prominence
    const rejectProminence = await this.assessRejectProminence(config);
    if (rejectProminence < 0.8) { // Less than 80% as prominent as accept
      score += 25;
      issues.push({
        type: 'unequal_prominence',
        severity: 'high',
        description: 'Reject option significantly less prominent than accept'
      });
    }

    // Check for pre-ticked boxes
    if (await this.hasPreTickedBoxes(config)) {
      score += 35;
      issues.push({
        type: 'pre_ticked',
        severity: 'critical',
        description: 'Pre-ticked consent boxes detected'
      });
    }

    // Check consent wall
    if (await this.hasConsentWall(config)) {
      score += 20;
      issues.push({
        type: 'consent_wall',
        severity: 'high',
        description: 'Consent wall blocks site access'
      });
    }

    return {
      name: 'Consent Mechanism',
      score: Math.min(100, score),
      weight: 0.25,
      issues,
      remediation: this.generateMechanismRemediation(issues)
    };
  }

  private calculateFinancialExposure(
    riskScore: number,
    config: RiskConfig
  ): FinancialExposure {
    // GDPR: Up to €20M or 4% of global turnover
    // CCPA: $2,500 per unintentional violation, $7,500 per intentional

    const jurisdictions = config.activeJurisdictions;
    let maxExposure = 0;
    let likelyExposure = 0;

    for (const jurisdiction of jurisdictions) {
      const { max, multiplier } = this.getJurisdictionFines(jurisdiction);
      const revenueBasedFine = config.globalRevenue * multiplier;
      const jurisdictionMax = Math.min(max, revenueBasedFine);

      maxExposure += jurisdictionMax;
      likelyExposure += jurisdictionMax * (riskScore / 100) * 0.3; // 30% realization rate
    }

    return {
      maximum: maxExposure,
      likely: likelyExposure,
      byJurisdiction: this.breakdownByJurisdiction(jurisdictions, riskScore, config),
      mitigationPotential: this.calculateMitigationPotential(riskScore)
    };
  }
}

3. Consent Withdrawal Metrics

High withdrawal rates signal problems—either with your consent collection or with user trust post-consent:

// Consent withdrawal analytics
class WithdrawalAnalytics {
  async analyzeWithdrawals(config: AnalyticsConfig): Promise<WithdrawalAnalysis> {
    const withdrawals = await this.getWithdrawals(config.dateRange);
    const totalConsents = await this.getTotalConsents(config.dateRange);

    // Overall withdrawal rate
    const overallRate = withdrawals.length / totalConsents;

    // Withdrawal by category
    const byCategory = this.aggregateByCategory(withdrawals);

    // Time to withdrawal analysis
    const timeToWithdrawal = this.analyzeTimeToWithdrawal(withdrawals);

    // Withdrawal reasons (if captured)
    const reasons = this.analyzeReasons(withdrawals);

    // Correlation analysis
    const correlations = await this.analyzeCorrelations(withdrawals, config);

    return {
      overallRate,
      benchmark: this.getBenchmark('withdrawal_rate'), // Industry benchmark
      trend: await this.calculateTrend(config),
      byCategory,
      timeToWithdrawal,
      reasons,
      correlations,
      insights: this.generateInsights({
        overallRate,
        byCategory,
        timeToWithdrawal,
        correlations
      }),
      recommendations: this.generateRecommendations({
        overallRate,
        byCategory,
        timeToWithdrawal,
        reasons
      })
    };
  }

  private analyzeTimeToWithdrawal(
    withdrawals: WithdrawalRecord[]
  ): TimeToWithdrawalAnalysis {
    const times = withdrawals.map(w => w.consentAge);

    // Segment into buckets
    const buckets = {
      immediate: times.filter(t => t < 3600000), // < 1 hour
      sameDay: times.filter(t => t >= 3600000 && t < 86400000),
      firstWeek: times.filter(t => t >= 86400000 && t < 604800000),
      firstMonth: times.filter(t => t >= 604800000 && t < 2592000000),
      later: times.filter(t => t >= 2592000000)
    };

    // Calculate percentiles
    const sorted = times.sort((a, b) => a - b);
    const median = sorted[Math.floor(sorted.length / 2)];
    const p25 = sorted[Math.floor(sorted.length * 0.25)];
    const p75 = sorted[Math.floor(sorted.length * 0.75)];

    // Identify problematic patterns
    const issues: string[] = [];

    if (buckets.immediate.length > withdrawals.length * 0.2) {
      issues.push('High immediate withdrawal rate suggests consent regret or dark patterns');
    }

    if (buckets.sameDay.length > withdrawals.length * 0.3) {
      issues.push('Many same-day withdrawals suggest post-consent experience issues');
    }

    return {
      distribution: {
        immediate: buckets.immediate.length / withdrawals.length,
        sameDay: buckets.sameDay.length / withdrawals.length,
        firstWeek: buckets.firstWeek.length / withdrawals.length,
        firstMonth: buckets.firstMonth.length / withdrawals.length,
        later: buckets.later.length / withdrawals.length
      },
      statistics: {
        median,
        p25,
        p75,
        mean: times.reduce((a, b) => a + b, 0) / times.length
      },
      issues,
      healthIndicator: this.calculateWithdrawalHealthIndicator(buckets, withdrawals.length)
    };
  }

  private async analyzeCorrelations(
    withdrawals: WithdrawalRecord[],
    config: AnalyticsConfig
  ): Promise<CorrelationAnalysis> {
    // Correlate withdrawals with various factors
    const correlations: Correlation[] = [];

    // Email marketing correlation
    const emailCorrelation = await this.correlateWithEmails(withdrawals);
    if (Math.abs(emailCorrelation.coefficient) > 0.3) {
      correlations.push({
        factor: 'email_marketing',
        coefficient: emailCorrelation.coefficient,
        significance: emailCorrelation.pValue,
        interpretation: emailCorrelation.coefficient > 0
          ? 'Withdrawals increase after email campaigns'
          : 'Withdrawals decrease after email campaigns'
      });
    }

    // Ad retargeting correlation
    const adCorrelation = await this.correlateWithAds(withdrawals);
    if (Math.abs(adCorrelation.coefficient) > 0.3) {
      correlations.push({
        factor: 'ad_retargeting',
        coefficient: adCorrelation.coefficient,
        significance: adCorrelation.pValue,
        interpretation: 'Aggressive retargeting correlates with withdrawals'
      });
    }

    // Data breach news correlation
    const newsCorrelation = await this.correlateWithPrivacyNews(withdrawals);
    if (Math.abs(newsCorrelation.coefficient) > 0.3) {
      correlations.push({
        factor: 'privacy_news',
        coefficient: newsCorrelation.coefficient,
        significance: newsCorrelation.pValue,
        interpretation: 'Privacy incidents in news correlate with withdrawals'
      });
    }

    return {
      correlations,
      strongestFactor: correlations.sort((a, b) =>
        Math.abs(b.coefficient) - Math.abs(a.coefficient)
      )[0],
      recommendations: this.generateCorrelationRecommendations(correlations)
    };
  }
}

4. Preference Center Engagement

A leading indicator of user trust and consent quality:

// Preference center analytics
class PreferenceCenterAnalytics {
  async analyzeEngagement(config: AnalyticsConfig): Promise<EngagementAnalysis> {
    const sessions = await this.getPreferenceCenterSessions(config.dateRange);

    // Basic engagement metrics
    const totalVisits = sessions.length;
    const uniqueUsers = new Set(sessions.map(s => s.userId)).size;
    const returningUsers = this.identifyReturningUsers(sessions);

    // Interaction depth
    const interactionMetrics = this.analyzeInteractions(sessions);

    // Funnel analysis
    const funnel = this.analyzeFunnel(sessions);

    // User journey mapping
    const journeys = this.mapUserJourneys(sessions);

    return {
      volume: {
        totalVisits,
        uniqueUsers,
        returningUsers: returningUsers.length,
        returnRate: returningUsers.length / uniqueUsers
      },
      engagement: {
        avgTimeSpent: this.calculateAvgTime(sessions),
        avgActionsPerSession: interactionMetrics.avgActions,
        preferencesChangedRate: interactionMetrics.changeRate,
        completionRate: funnel.completionRate
      },
      funnel,
      journeys,
      insights: this.generateEngagementInsights({
        volume: { totalVisits, uniqueUsers, returningUsers },
        interactionMetrics,
        funnel
      }),
      benchmarks: {
        industryAvgTime: 45, // seconds
        industryChangeRate: 0.15,
        yourRanking: this.calculateIndustryRanking(sessions)
      }
    };
  }

  private analyzeFunnel(sessions: PreferenceCenterSession[]): FunnelAnalysis {
    const stages = {
      opened: sessions.length,
      viewedCategories: sessions.filter(s => s.events.some(e => e.type === 'view_category')).length,
      toggledPreference: sessions.filter(s => s.events.some(e => e.type === 'toggle')).length,
      savedChanges: sessions.filter(s => s.events.some(e => e.type === 'save')).length,
      confirmedChanges: sessions.filter(s => s.outcome === 'saved').length
    };

    const dropoffs = {
      afterOpen: (stages.opened - stages.viewedCategories) / stages.opened,
      afterView: (stages.viewedCategories - stages.toggledPreference) / stages.viewedCategories,
      afterToggle: (stages.toggledPreference - stages.savedChanges) / stages.toggledPreference,
      afterSave: (stages.savedChanges - stages.confirmedChanges) / stages.savedChanges
    };

    // Identify biggest drop-off
    const biggestDropoff = Object.entries(dropoffs)
      .sort(([, a], [, b]) => b - a)[0];

    return {
      stages,
      dropoffs,
      completionRate: stages.confirmedChanges / stages.opened,
      biggestDropoff: {
        stage: biggestDropoff[0],
        rate: biggestDropoff[1],
        recommendation: this.getDropoffRecommendation(biggestDropoff[0])
      }
    };
  }
}

5. Business Impact Metrics

Connect consent to business outcomes:

// Business impact analytics
class BusinessImpactAnalytics {
  async analyzeBusinessImpact(config: AnalyticsConfig): Promise<BusinessImpact> {
    // Revenue impact
    const revenueImpact = await this.analyzeRevenueImpact(config);

    // Marketing effectiveness
    const marketingImpact = await this.analyzeMarketingImpact(config);

    // Analytics coverage
    const analyticsCoverage = await this.analyzeAnalyticsCoverage(config);

    // Cost of consent management
    const programCosts = await this.calculateProgramCosts(config);

    // ROI calculation
    const roi = this.calculateConsentProgramROI({
      revenueImpact,
      marketingImpact,
      programCosts
    });

    return {
      revenue: revenueImpact,
      marketing: marketingImpact,
      analytics: analyticsCoverage,
      costs: programCosts,
      roi,
      recommendations: this.generateBusinessRecommendations({
        revenueImpact,
        marketingImpact,
        analyticsCoverage,
        roi
      })
    };
  }

  private async analyzeRevenueImpact(config: AnalyticsConfig): Promise<RevenueImpact> {
    // Segment revenue by consent status
    const consentedRevenue = await this.getRevenue({ consentStatus: 'full' });
    const partialConsentRevenue = await this.getRevenue({ consentStatus: 'partial' });
    const noConsentRevenue = await this.getRevenue({ consentStatus: 'none' });

    const totalRevenue = consentedRevenue + partialConsentRevenue + noConsentRevenue;

    // Calculate conversion rates by consent
    const conversionRates = {
      fullConsent: await this.getConversionRate({ consentStatus: 'full' }),
      partialConsent: await this.getConversionRate({ consentStatus: 'partial' }),
      noConsent: await this.getConversionRate({ consentStatus: 'none' })
    };

    // Calculate revenue at risk
    const revenueAtRisk = this.calculateRevenueAtRisk({
      marketingRevenue: await this.getMarketingAttributedRevenue(),
      consentRate: config.currentConsentRate,
      projectedConsentRate: config.projectedConsentRate
    });

    // Calculate opportunity cost of consent friction
    const frictionCost = await this.calculateFrictionCost(config);

    return {
      breakdown: {
        fullConsent: consentedRevenue,
        partialConsent: partialConsentRevenue,
        noConsent: noConsentRevenue
      },
      percentages: {
        fullConsent: consentedRevenue / totalRevenue,
        partialConsent: partialConsentRevenue / totalRevenue,
        noConsent: noConsentRevenue / totalRevenue
      },
      conversionRates,
      revenueAtRisk,
      frictionCost,
      insights: this.generateRevenueInsights({
        conversionRates,
        revenueAtRisk,
        frictionCost
      })
    };
  }

  private async analyzeMarketingImpact(config: AnalyticsConfig): Promise<MarketingImpact> {
    // Audience reach impact
    const audienceMetrics = await this.getAudienceMetrics(config);

    // Retargeting effectiveness
    const retargetingMetrics = {
      eligibleAudience: audienceMetrics.marketingConsent,
      actualReach: await this.getActualMarketingReach(),
      conversionRate: await this.getRetargetingConversionRate(),
      costPerConversion: await this.getRetargetingCPC()
    };

    // Attribution impact
    const attributionMetrics = {
      attributableConversions: await this.getAttributableConversions(),
      darkFunnel: await this.getDarkFunnelEstimate(), // Untracked due to no consent
      attributionAccuracy: await this.getAttributionAccuracy()
    };

    // Calculate marketing value of consent
    const consentValue = this.calculateMarketingConsentValue({
      retargeting: retargetingMetrics,
      attribution: attributionMetrics,
      avgCustomerValue: config.avgCustomerValue
    });

    return {
      audienceReach: {
        potential: audienceMetrics.totalUsers,
        marketingEligible: audienceMetrics.marketingConsent,
        percentage: audienceMetrics.marketingConsent / audienceMetrics.totalUsers
      },
      retargeting: retargetingMetrics,
      attribution: attributionMetrics,
      consentValue,
      recommendations: this.generateMarketingRecommendations({
        audienceMetrics,
        retargetingMetrics,
        attributionMetrics
      })
    };
  }

  private calculateConsentProgramROI(data: ROIData): ROIAnalysis {
    // Benefits
    const riskReduction = data.revenueImpact.revenueAtRisk * 0.7; // 70% risk reduction
    const marketingValue = data.marketingImpact?.consentValue || 0;
    const avoidedFines = this.estimateAvoidedFines(data);
    const totalBenefits = riskReduction + marketingValue + avoidedFines;

    // Costs
    const totalCosts = data.programCosts.total;

    // ROI
    const roi = (totalBenefits - totalCosts) / totalCosts;

    return {
      benefits: {
        riskReduction,
        marketingValue,
        avoidedFines,
        total: totalBenefits
      },
      costs: data.programCosts,
      roi,
      paybackPeriod: totalCosts / (totalBenefits / 12), // In months
      interpretation: this.interpretROI(roi)
    };
  }
}

Building a Consent Analytics Dashboard

Dashboard Architecture

// Consent analytics dashboard
class ConsentAnalyticsDashboard {
  private qualityScorer: ConsentQualityScorer;
  private riskScorer: RegulatoryRiskScorer;
  private withdrawalAnalytics: WithdrawalAnalytics;
  private preferenceCenterAnalytics: PreferenceCenterAnalytics;
  private businessImpact: BusinessImpactAnalytics;

  async generateDashboard(config: DashboardConfig): Promise<Dashboard> {
    // Fetch all metrics in parallel
    const [
      qualityMetrics,
      riskMetrics,
      withdrawalMetrics,
      engagementMetrics,
      businessMetrics
    ] = await Promise.all([
      this.getQualityMetrics(config),
      this.getRiskMetrics(config),
      this.getWithdrawalMetrics(config),
      this.getEngagementMetrics(config),
      this.getBusinessMetrics(config)
    ]);

    // Generate executive summary
    const executiveSummary = this.generateExecutiveSummary({
      quality: qualityMetrics,
      risk: riskMetrics,
      withdrawal: withdrawalMetrics,
      engagement: engagementMetrics,
      business: businessMetrics
    });

    // Generate trend analysis
    const trends = await this.generateTrends(config);

    // Generate alerts
    const alerts = this.generateAlerts({
      quality: qualityMetrics,
      risk: riskMetrics,
      withdrawal: withdrawalMetrics
    });

    // Generate recommendations
    const recommendations = this.prioritizeRecommendations({
      quality: qualityMetrics,
      risk: riskMetrics,
      withdrawal: withdrawalMetrics,
      engagement: engagementMetrics,
      business: businessMetrics
    });

    return {
      generatedAt: new Date().toISOString(),
      period: config.dateRange,
      executiveSummary,
      sections: {
        overview: this.generateOverview({
          quality: qualityMetrics,
          risk: riskMetrics,
          business: businessMetrics
        }),
        quality: qualityMetrics,
        risk: riskMetrics,
        withdrawal: withdrawalMetrics,
        engagement: engagementMetrics,
        business: businessMetrics,
        trends,
        geographic: await this.generateGeographicAnalysis(config),
        comparative: await this.generateComparativeAnalysis(config)
      },
      alerts,
      recommendations,
      exportFormats: ['pdf', 'excel', 'json']
    };
  }

  private generateExecutiveSummary(data: MetricsData): ExecutiveSummary {
    // Overall health score (0-100)
    const healthScore = this.calculateOverallHealth(data);

    // Key highlights
    const highlights: Highlight[] = [];

    // Positive highlights
    if (data.quality.overall > 0.7) {
      highlights.push({
        type: 'positive',
        metric: 'Consent Quality',
        message: `High-quality consent at ${(data.quality.overall * 100).toFixed(1)}%`
      });
    }

    if (data.withdrawal.overallRate < 0.05) {
      highlights.push({
        type: 'positive',
        metric: 'Withdrawal Rate',
        message: `Low withdrawal rate of ${(data.withdrawal.overallRate * 100).toFixed(1)}%`
      });
    }

    // Warning highlights
    if (data.risk.overallScore > 50) {
      highlights.push({
        type: 'warning',
        metric: 'Regulatory Risk',
        message: `Elevated risk score of ${data.risk.overallScore.toFixed(0)}/100`,
        action: 'Review risk factors immediately'
      });
    }

    if (data.quality.components.deliberate < 0.4) {
      highlights.push({
        type: 'warning',
        metric: 'Decision Time',
        message: 'Users making decisions too quickly',
        action: 'Review consent UX for clarity'
      });
    }

    // Critical highlights
    if (data.risk.factors.some(f => f.score > 80)) {
      const criticalFactor = data.risk.factors.find(f => f.score > 80);
      highlights.push({
        type: 'critical',
        metric: criticalFactor!.name,
        message: `Critical issue: ${criticalFactor!.name}`,
        action: 'Immediate remediation required'
      });
    }

    return {
      healthScore,
      healthTrend: this.calculateHealthTrend(data),
      highlights,
      keyMetrics: {
        consentRate: data.quality.consentRate,
        qualityScore: data.quality.overall,
        riskScore: data.risk.overallScore,
        withdrawalRate: data.withdrawal.overallRate,
        roi: data.business.roi.roi
      },
      periodComparison: this.generatePeriodComparison(data),
      topPriority: this.identifyTopPriority(data)
    };
  }

  private generateAlerts(data: AlertData): Alert[] {
    const alerts: Alert[] = [];

    // Quality alerts
    if (data.quality.overall < 0.5) {
      alerts.push({
        severity: 'high',
        category: 'quality',
        title: 'Low Consent Quality Score',
        description: `Quality score of ${(data.quality.overall * 100).toFixed(1)}% is below acceptable threshold`,
        impact: 'Consent may not be legally defensible',
        recommendation: 'Review consent UX and information disclosure',
        trend: data.quality.trend
      });
    }

    // Risk alerts
    for (const factor of data.risk.factors) {
      if (factor.score > 70) {
        alerts.push({
          severity: factor.score > 85 ? 'critical' : 'high',
          category: 'risk',
          title: `High Risk: ${factor.name}`,
          description: factor.issues.map(i => i.description).join('; '),
          impact: `Potential exposure: ${this.formatCurrency(factor.exposure)}`,
          recommendation: factor.remediation,
          trend: factor.trend
        });
      }
    }

    // Withdrawal alerts
    if (data.withdrawal.overallRate > 0.1) {
      alerts.push({
        severity: data.withdrawal.overallRate > 0.2 ? 'high' : 'medium',
        category: 'withdrawal',
        title: 'Elevated Withdrawal Rate',
        description: `${(data.withdrawal.overallRate * 100).toFixed(1)}% of users are withdrawing consent`,
        impact: 'Indicates trust issues or consent regret',
        recommendation: data.withdrawal.correlations.strongestFactor
          ? `Investigate ${data.withdrawal.correlations.strongestFactor.factor}`
          : 'Analyze withdrawal patterns for root cause'
      });
    }

    // Trend alerts
    const qualityTrendDecline = data.quality.trend?.change < -0.1;
    if (qualityTrendDecline) {
      alerts.push({
        severity: 'medium',
        category: 'trend',
        title: 'Quality Score Declining',
        description: `Quality score has dropped ${Math.abs(data.quality.trend.change * 100).toFixed(1)}% this period`,
        impact: 'May indicate degradation in consent experience',
        recommendation: 'Investigate recent changes to consent flow'
      });
    }

    return alerts.sort((a, b) => {
      const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
      return severityOrder[a.severity] - severityOrder[b.severity];
    });
  }
}

Geographic Segmentation

// Geographic consent analytics
class GeographicAnalytics {
  async analyzeByRegion(config: AnalyticsConfig): Promise<GeographicAnalysis> {
    const regions = await this.getActiveRegions(config);
    const regionMetrics: RegionMetrics[] = [];

    for (const region of regions) {
      const metrics = await this.getRegionMetrics(region, config);

      regionMetrics.push({
        region,
        regulation: this.getApplicableRegulation(region),
        volume: metrics.volume,
        consentRate: metrics.consentRate,
        qualityScore: metrics.qualityScore,
        withdrawalRate: metrics.withdrawalRate,
        riskLevel: this.assessRegionalRisk(region, metrics),
        benchmark: this.getRegionalBenchmark(region)
      });
    }

    // Identify outliers
    const outliers = this.identifyOutliers(regionMetrics);

    // Generate regional recommendations
    const recommendations = this.generateRegionalRecommendations(regionMetrics);

    return {
      summary: {
        totalRegions: regions.length,
        highRiskRegions: regionMetrics.filter(r => r.riskLevel === 'high').length,
        bestPerforming: regionMetrics.sort((a, b) => b.qualityScore - a.qualityScore)[0],
        worstPerforming: regionMetrics.sort((a, b) => a.qualityScore - b.qualityScore)[0]
      },
      regions: regionMetrics,
      outliers,
      heatmap: this.generateHeatmapData(regionMetrics),
      recommendations
    };
  }

  private assessRegionalRisk(region: string, metrics: RegionMetricsData): RiskLevel {
    // High enforcement regions
    const highEnforcementRegions = ['DE', 'FR', 'IT', 'ES', 'NL', 'BE'];
    const isHighEnforcement = highEnforcementRegions.includes(region);

    // Calculate risk factors
    let riskScore = 0;

    if (metrics.consentRate < 0.6) riskScore += 20;
    if (metrics.qualityScore < 0.5) riskScore += 25;
    if (metrics.withdrawalRate > 0.1) riskScore += 15;
    if (isHighEnforcement) riskScore += 20;

    // Check for specific regional issues
    const regionalIssues = this.getRegionalIssues(region);
    riskScore += regionalIssues.length * 10;

    if (riskScore >= 60) return 'high';
    if (riskScore >= 40) return 'medium';
    return 'low';
  }
}

Implementing Consent Analytics

Data Collection Architecture

// Consent event tracking
class ConsentEventTracker {
  private eventQueue: ConsentEvent[] = [];
  private batchSize = 100;
  private flushInterval = 5000; // 5 seconds

  constructor(private config: TrackerConfig) {
    this.startPeriodicFlush();
  }

  trackConsentInteraction(interaction: ConsentInteraction): void {
    const event: ConsentEvent = {
      id: this.generateEventId(),
      timestamp: Date.now(),
      sessionId: this.getSessionId(),
      userId: this.getUserId(),
      type: interaction.type,
      data: this.sanitizeData(interaction.data),
      context: {
        page: window.location.pathname,
        referrer: document.referrer,
        device: this.getDeviceInfo(),
        locale: navigator.language,
        timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
      },
      timing: {
        pageLoadTime: performance.now(),
        bannerDisplayTime: this.getBannerDisplayTime(),
        interactionTime: this.getInteractionTime()
      }
    };

    this.eventQueue.push(event);

    if (this.eventQueue.length >= this.batchSize) {
      this.flush();
    }
  }

  trackConsentDecision(decision: ConsentDecision): void {
    // Calculate quality metrics at decision time
    const qualityIndicators = {
      timeToDecision: performance.now() - this.getBannerDisplayTime(),
      informationViewed: this.getViewedInformation(),
      interactionsCount: this.getInteractionCount(),
      scrollDepth: this.getScrollDepth(),
      categorySelections: decision.categories
    };

    this.trackConsentInteraction({
      type: 'decision',
      data: {
        decision: decision.type, // 'accept_all' | 'reject_all' | 'custom'
        categories: decision.categories,
        qualityIndicators,
        consentString: decision.consentString,
        tcfData: decision.tcfData
      }
    });
  }

  trackPreferenceCenterInteraction(interaction: PreferenceCenterInteraction): void {
    this.trackConsentInteraction({
      type: 'preference_center',
      data: {
        action: interaction.action,
        category: interaction.category,
        previousValue: interaction.previousValue,
        newValue: interaction.newValue,
        sessionInteractions: this.getSessionInteractionCount()
      }
    });
  }

  trackWithdrawal(withdrawal: ConsentWithdrawal): void {
    this.trackConsentInteraction({
      type: 'withdrawal',
      data: {
        withdrawnCategories: withdrawal.categories,
        reason: withdrawal.reason,
        method: withdrawal.method, // 'preference_center' | 'footer_link' | 'account_settings'
        originalConsentDate: withdrawal.originalConsentDate,
        consentDuration: Date.now() - withdrawal.originalConsentDate
      }
    });
  }

  private async flush(): Promise<void> {
    if (this.eventQueue.length === 0) return;

    const events = [...this.eventQueue];
    this.eventQueue = [];

    try {
      await this.sendEvents(events);
    } catch (error) {
      // Re-queue failed events
      this.eventQueue = [...events, ...this.eventQueue];
      console.error('Failed to send consent events:', error);
    }
  }

  private async sendEvents(events: ConsentEvent[]): Promise<void> {
    await fetch(this.config.endpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.config.apiKey
      },
      body: JSON.stringify({
        events,
        clientInfo: {
          sdkVersion: this.config.sdkVersion,
          siteId: this.config.siteId
        }
      })
    });
  }
}

Real-Time Monitoring

// Real-time consent monitoring
class RealTimeConsentMonitor {
  private websocket: WebSocket;
  private alertHandlers: Map<string, AlertHandler> = new Map();
  private metrics: RealTimeMetrics;

  async startMonitoring(config: MonitorConfig): Promise<void> {
    // Connect to real-time data stream
    this.websocket = new WebSocket(config.streamUrl);

    this.websocket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this.processEvent(data);
    };

    // Initialize metrics
    this.metrics = {
      currentConsentRate: 0,
      currentQualityScore: 0,
      eventsPerMinute: 0,
      activeAlerts: []
    };

    // Start metric calculations
    this.startMetricCalculations();
  }

  private processEvent(event: ConsentStreamEvent): void {
    // Update rolling metrics
    this.updateRollingMetrics(event);

    // Check alert conditions
    this.checkAlertConditions(event);

    // Emit to subscribers
    this.emitToSubscribers(event);
  }

  private checkAlertConditions(event: ConsentStreamEvent): void {
    // Check for anomalies
    if (event.type === 'decision') {
      // Sudden consent rate drop
      if (this.metrics.currentConsentRate < this.metrics.baselineConsentRate * 0.8) {
        this.triggerAlert({
          type: 'consent_rate_drop',
          severity: 'high',
          message: `Consent rate dropped to ${(this.metrics.currentConsentRate * 100).toFixed(1)}%`,
          data: {
            current: this.metrics.currentConsentRate,
            baseline: this.metrics.baselineConsentRate
          }
        });
      }

      // Quality score drop
      if (this.metrics.currentQualityScore < 0.4) {
        this.triggerAlert({
          type: 'quality_score_low',
          severity: 'medium',
          message: 'Low consent quality detected',
          data: {
            score: this.metrics.currentQualityScore
          }
        });
      }
    }

    // Withdrawal spike
    if (event.type === 'withdrawal') {
      this.metrics.recentWithdrawals.push(event.timestamp);
      const withdrawalsLastHour = this.metrics.recentWithdrawals.filter(
        t => t > Date.now() - 3600000
      ).length;

      if (withdrawalsLastHour > this.metrics.baselineWithdrawalsPerHour * 2) {
        this.triggerAlert({
          type: 'withdrawal_spike',
          severity: 'high',
          message: `Withdrawal spike detected: ${withdrawalsLastHour} in last hour`,
          data: {
            current: withdrawalsLastHour,
            baseline: this.metrics.baselineWithdrawalsPerHour
          }
        });
      }
    }
  }

  registerAlertHandler(alertType: string, handler: AlertHandler): void {
    this.alertHandlers.set(alertType, handler);
  }

  private triggerAlert(alert: Alert): void {
    // Check if alert is already active
    if (this.metrics.activeAlerts.find(a => a.type === alert.type)) {
      return;
    }

    this.metrics.activeAlerts.push(alert);

    // Call registered handler
    const handler = this.alertHandlers.get(alert.type);
    if (handler) {
      handler(alert);
    }

    // Send notification
    this.sendAlertNotification(alert);
  }
}

Benchmarks and Industry Standards

Metric Poor Fair Good Excellent
Consent Quality Score < 0.4 0.4-0.6 0.6-0.8 > 0.8
Overall Consent Rate (illustrative targets) < 50% 50-70% 70-85% > 85%
Withdrawal Rate (Annual) > 15% 10-15% 5-10% < 5%
Preference Center Return Rate < 1% 1-3% 3-5% > 5%
Average Decision Time < 2s 2-5s 5-15s 15-30s
Regulatory Risk Score > 70 50-70 30-50 < 30
Marketing Consent Rate < 30% 30-50% 50-65% > 65%

Frequently Asked Questions

What's more important: consent rate or consent quality?

Quality should always take priority. A 95% consent rate achieved through dark patterns creates significant regulatory risk and may be invalidated. A 70% consent rate with high quality scores is far more defensible and sustainable. Focus on quality first; rates will follow naturally with good UX.

How often should we review consent analytics?

Real-time monitoring for anomalies, weekly operational reviews, monthly strategic reviews, and quarterly deep-dive analyses with stakeholder presentations. The frequency should increase during regulatory changes, site updates, or after any changes to consent mechanisms.

What's a healthy withdrawal rate?

There is no published industry benchmark; track your own rate over time. Withdrawal patterns matter more than raw rates—immediate withdrawals suggest consent regret (possibly dark patterns), while gradual withdrawals over months may indicate changing user preferences or post-consent experience issues.

How do we calculate ROI for consent programs?

ROI = (Risk reduction value + Marketing data value + Avoided fines - Program costs) / Program costs. Risk reduction value is the reduction in potential fine exposure times probability. Marketing data value is the incremental revenue from consented marketing activities.

The Path to Privacy Excellence

Move beyond consent rate as your primary metric. The metrics outlined here provide a comprehensive view of privacy program health and predict both regulatory outcomes and business results.

Organizations that embrace comprehensive consent analytics don't just avoid fines—they build user trust that becomes a competitive advantage. In a world of increasing privacy awareness, the companies that genuinely respect user choices will win.

Start by implementing the Consent Quality Score. Add regulatory risk tracking. Build out your business impact analysis. Within six months, you'll have transformed consent management from a compliance checkbox to a strategic business capability.

The future belongs to organizations that measure what matters—not just what's easy to count.

Additional Resources

Häufig gestellte Fragen

What is a good cookie consent rate?
While it varies by industry, a "good" informed consent rate typically ranges between 50-70%. Rates above 90% often indicate the presence of dark patterns (like hiding the reject button), which is a compliance risk.
G

GetCookies-Team

Geschrieben und gepflegt vom GetCookies-Team der Getia AS in Oslo, Norwegen. Dieser Artikel ist eine allgemeine Information zur Cookie-Einwilligung und keine Rechtsberatung.

Scannen Sie Ihre eigene Website kostenlos

Im kostenlosen Plan läuft das Banner auf einer Website, dazu drei Cookie-Scans pro Monat. Keine Kreditkarte, kein Verkaufsgespräch.