Compliance

CCPA Technical Compliance: Implementation Guide

DeviDevs Team
9 min read
#ccpa#privacy#compliance#data-protection#consumer-rights

CCPA gives California consumers significant rights over their personal information. This guide covers technical implementation of CCPA compliance including consumer rights handling, data inventory, and opt-out mechanisms.

Consumer Rights Management System

Build a system to handle CCPA consumer requests:

from dataclasses import dataclass, field
from typing import List, Dict, Optional, Set
from enum import Enum
from datetime import datetime, timedelta
from abc import ABC, abstractmethod
import json
import hashlib
 
class CCPARight(Enum):
    KNOW = "right_to_know"
    DELETE = "right_to_delete"
    OPT_OUT = "right_to_opt_out"
    NON_DISCRIMINATION = "right_to_non_discrimination"
    CORRECT = "right_to_correct"
    LIMIT_USE = "right_to_limit_use"
 
class RequestStatus(Enum):
    RECEIVED = "received"
    VERIFYING = "verifying"
    VERIFIED = "verified"
    PROCESSING = "processing"
    COMPLETED = "completed"
    DENIED = "denied"
 
class PersonalInfoCategory(Enum):
    IDENTIFIERS = "identifiers"
    COMMERCIAL_INFO = "commercial_information"
    BIOMETRIC = "biometric_information"
    INTERNET_ACTIVITY = "internet_activity"
    GEOLOCATION = "geolocation_data"
    AUDIO_VISUAL = "audio_visual_information"
    PROFESSIONAL_INFO = "professional_information"
    EDUCATION_INFO = "education_information"
    INFERENCES = "inferences"
    SENSITIVE_PI = "sensitive_personal_information"
 
@dataclass
class ConsumerRequest:
    request_id: str
    consumer_id: str
    right_type: CCPARight
    submitted_at: datetime
    deadline: datetime
    status: RequestStatus
    verification_method: str
    verified_at: Optional[datetime] = None
    completed_at: Optional[datetime] = None
    response_sent_at: Optional[datetime] = None
    categories_requested: List[PersonalInfoCategory] = field(default_factory=list)
    denial_reason: Optional[str] = None
    notes: List[str] = field(default_factory=list)
 
@dataclass
class DataInventoryEntry:
    category: PersonalInfoCategory
    source: str
    business_purpose: str
    third_parties_shared: List[str]
    retention_period_days: int
    sold_or_shared: bool
 
class CCPAComplianceManager:
    def __init__(self):
        self.requests: Dict[str, ConsumerRequest] = {}
        self.data_inventory: Dict[PersonalInfoCategory, List[DataInventoryEntry]] = {}
        self.opted_out_consumers: Set[str] = set()
        self.deadline_days = 45
        self.extension_days = 45
        self._initialize_data_inventory()
 
    def _initialize_data_inventory(self):
        """Initialize data inventory categories."""
        self.data_inventory = {
            PersonalInfoCategory.IDENTIFIERS: [
                DataInventoryEntry(
                    category=PersonalInfoCategory.IDENTIFIERS,
                    source="direct_collection",
                    business_purpose="Account creation and management",
                    third_parties_shared=["Payment processor", "Email service"],
                    retention_period_days=1095,
                    sold_or_shared=False
                )
            ],
            PersonalInfoCategory.COMMERCIAL_INFO: [
                DataInventoryEntry(
                    category=PersonalInfoCategory.COMMERCIAL_INFO,
                    source="transactions",
                    business_purpose="Order fulfillment and customer service",
                    third_parties_shared=["Shipping provider", "Analytics"],
                    retention_period_days=2555,
                    sold_or_shared=False
                )
            ],
            PersonalInfoCategory.INTERNET_ACTIVITY: [
                DataInventoryEntry(
                    category=PersonalInfoCategory.INTERNET_ACTIVITY,
                    source="automatic_collection",
                    business_purpose="Website optimization and security",
                    third_parties_shared=["Analytics provider", "CDN"],
                    retention_period_days=365,
                    sold_or_shared=True
                )
            ]
        }
 
    def submit_request(
        self,
        consumer_id: str,
        right_type: CCPARight,
        categories: List[PersonalInfoCategory] = None,
        verification_method: str = "email"
    ) -> ConsumerRequest:
        """Submit a new consumer request."""
        request_id = self._generate_request_id()
 
        request = ConsumerRequest(
            request_id=request_id,
            consumer_id=consumer_id,
            right_type=right_type,
            submitted_at=datetime.utcnow(),
            deadline=datetime.utcnow() + timedelta(days=self.deadline_days),
            status=RequestStatus.RECEIVED,
            verification_method=verification_method,
            categories_requested=categories or list(PersonalInfoCategory)
        )
 
        self.requests[request_id] = request
        self._initiate_verification(request)
 
        return request
 
    def _initiate_verification(self, request: ConsumerRequest):
        """Initiate consumer verification process."""
        request.status = RequestStatus.VERIFYING
 
        if request.verification_method == "email":
            self._send_verification_email(request)
        elif request.verification_method == "account":
            self._verify_via_account(request)
 
    def verify_consumer(self, request_id: str, verification_code: str) -> bool:
        """Verify consumer identity."""
        request = self.requests.get(request_id)
        if not request:
            return False
 
        if self._validate_verification_code(request, verification_code):
            request.status = RequestStatus.VERIFIED
            request.verified_at = datetime.utcnow()
            self._process_request(request)
            return True
 
        return False
 
    def _process_request(self, request: ConsumerRequest):
        """Process verified request based on type."""
        request.status = RequestStatus.PROCESSING
 
        handlers = {
            CCPARight.KNOW: self._handle_know_request,
            CCPARight.DELETE: self._handle_delete_request,
            CCPARight.OPT_OUT: self._handle_opt_out_request,
            CCPARight.CORRECT: self._handle_correct_request,
            CCPARight.LIMIT_USE: self._handle_limit_use_request
        }
 
        handler = handlers.get(request.right_type)
        if handler:
            handler(request)
 
    def _handle_know_request(self, request: ConsumerRequest):
        """Handle Right to Know request."""
        report = {
            'request_id': request.request_id,
            'consumer_id': request.consumer_id,
            'generated_at': datetime.utcnow().isoformat(),
            'categories_collected': [],
            'specific_pieces': {},
            'sources': [],
            'business_purposes': [],
            'third_parties': [],
            'selling_disclosure': {}
        }
 
        for category in request.categories_requested:
            entries = self.data_inventory.get(category, [])
            for entry in entries:
                if category not in [c['category'] for c in report['categories_collected']]:
                    report['categories_collected'].append({
                        'category': category.value,
                        'description': self._get_category_description(category)
                    })
 
                report['sources'].append(entry.source)
                report['business_purposes'].append(entry.business_purpose)
                report['third_parties'].extend(entry.third_parties_shared)
 
                if entry.sold_or_shared:
                    report['selling_disclosure'][category.value] = {
                        'sold_or_shared': True,
                        'recipients': entry.third_parties_shared
                    }
 
        report['sources'] = list(set(report['sources']))
        report['business_purposes'] = list(set(report['business_purposes']))
        report['third_parties'] = list(set(report['third_parties']))
 
        self._send_know_response(request, report)
        request.status = RequestStatus.COMPLETED
        request.completed_at = datetime.utcnow()
 
    def _handle_delete_request(self, request: ConsumerRequest):
        """Handle Right to Delete request."""
        deletion_results = {
            'deleted': [],
            'retained': [],
            'errors': []
        }
 
        for category in request.categories_requested:
            can_delete, reason = self._can_delete_category(category, request.consumer_id)
 
            if can_delete:
                try:
                    self._delete_consumer_data(request.consumer_id, category)
                    deletion_results['deleted'].append(category.value)
                except Exception as e:
                    deletion_results['errors'].append({
                        'category': category.value,
                        'error': str(e)
                    })
            else:
                deletion_results['retained'].append({
                    'category': category.value,
                    'reason': reason
                })
 
        self._notify_service_providers_deletion(request.consumer_id)
 
        self._send_delete_response(request, deletion_results)
        request.status = RequestStatus.COMPLETED
        request.completed_at = datetime.utcnow()
 
    def _can_delete_category(self, category: PersonalInfoCategory, consumer_id: str) -> tuple:
        """Check if data category can be deleted."""
        exceptions = {
            'complete_transaction': "Required to complete transaction",
            'security': "Required for security purposes",
            'legal_obligation': "Required by law to retain",
            'internal_use': "Used solely for internal purposes"
        }
 
        if category == PersonalInfoCategory.COMMERCIAL_INFO:
            if self._has_pending_transactions(consumer_id):
                return (False, exceptions['complete_transaction'])
 
        if category == PersonalInfoCategory.IDENTIFIERS:
            if self._has_legal_hold(consumer_id):
                return (False, exceptions['legal_obligation'])
 
        return (True, None)
 
    def _handle_opt_out_request(self, request: ConsumerRequest):
        """Handle Right to Opt-Out of Sale request."""
        self.opted_out_consumers.add(request.consumer_id)
 
        self._update_advertising_preferences(request.consumer_id, opted_out=True)
        self._notify_data_partners_opt_out(request.consumer_id)
        self._set_gpc_preference(request.consumer_id, True)
 
        self._send_opt_out_confirmation(request)
        request.status = RequestStatus.COMPLETED
        request.completed_at = datetime.utcnow()
 
    def _handle_correct_request(self, request: ConsumerRequest):
        """Handle Right to Correct request."""
        # Implementation for data correction
        pass
 
    def _handle_limit_use_request(self, request: ConsumerRequest):
        """Handle Right to Limit Use of Sensitive PI."""
        # Implementation for limiting sensitive PI use
        pass
 
    def check_opt_out_status(self, consumer_id: str) -> bool:
        """Check if consumer has opted out of sale."""
        return consumer_id in self.opted_out_consumers
 
    def _get_category_description(self, category: PersonalInfoCategory) -> str:
        descriptions = {
            PersonalInfoCategory.IDENTIFIERS: "Real name, alias, postal address, email, IP address, account name",
            PersonalInfoCategory.COMMERCIAL_INFO: "Products purchased, obtained, or considered; purchasing histories",
            PersonalInfoCategory.INTERNET_ACTIVITY: "Browsing history, search history, interaction with websites or apps",
            PersonalInfoCategory.GEOLOCATION: "Physical location or movements",
            PersonalInfoCategory.PROFESSIONAL_INFO: "Employment-related information",
            PersonalInfoCategory.SENSITIVE_PI: "Government IDs, financial accounts, precise geolocation, racial/ethnic origin"
        }
        return descriptions.get(category, category.value)
 
    def _generate_request_id(self) -> str:
        return f"CCPA-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{hashlib.sha256(str(datetime.utcnow().timestamp()).encode()).hexdigest()[:8]}"
 
    # Placeholder methods
    def _send_verification_email(self, request): pass
    def _verify_via_account(self, request): pass
    def _validate_verification_code(self, request, code): return True
    def _send_know_response(self, request, report): pass
    def _send_delete_response(self, request, results): pass
    def _send_opt_out_confirmation(self, request): pass
    def _delete_consumer_data(self, consumer_id, category): pass
    def _notify_service_providers_deletion(self, consumer_id): pass
    def _has_pending_transactions(self, consumer_id): return False
    def _has_legal_hold(self, consumer_id): return False
    def _update_advertising_preferences(self, consumer_id, opted_out): pass
    def _notify_data_partners_opt_out(self, consumer_id): pass
    def _set_gpc_preference(self, consumer_id, value): pass

Global Privacy Control (GPC) Handler

Implement GPC signal detection:

class GPCHandler:
    """Handle Global Privacy Control signals."""
 
    def __init__(self, ccpa_manager: CCPAComplianceManager):
        self.ccpa_manager = ccpa_manager
 
    def check_gpc_signal(self, request_headers: Dict[str, str]) -> bool:
        """Check for GPC signal in request headers."""
        gpc_header = request_headers.get('Sec-GPC', '')
        return gpc_header == '1'
 
    def process_gpc_request(self, request_headers: Dict, user_context: Dict) -> Dict:
        """Process GPC signal and apply opt-out."""
        if not self.check_gpc_signal(request_headers):
            return {'gpc_detected': False}
 
        consumer_id = user_context.get('consumer_id')
        if not consumer_id:
            return {
                'gpc_detected': True,
                'action': 'anonymous_opt_out',
                'cookies_blocked': True
            }
 
        if not self.ccpa_manager.check_opt_out_status(consumer_id):
            self.ccpa_manager.submit_request(
                consumer_id=consumer_id,
                right_type=CCPARight.OPT_OUT,
                verification_method="automatic_gpc"
            )
 
        return {
            'gpc_detected': True,
            'action': 'opt_out_applied',
            'consumer_id': consumer_id
        }
 
    def get_gpc_javascript(self) -> str:
        """Generate JavaScript for GPC detection."""
        return """
        (function() {
            // Check for GPC signal
            const gpcEnabled = navigator.globalPrivacyControl;
 
            if (gpcEnabled) {
                // Disable tracking cookies
                document.cookie = '_tracking=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
 
                // Notify backend of GPC
                fetch('/api/privacy/gpc', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ gpc: true })
                });
 
                // Disable third-party scripts
                window.disableTracking = true;
            }
        })();
        """

Build the required Do Not Sell interface:

from flask import Flask, request, jsonify, render_template
 
app = Flask(__name__)
ccpa_manager = CCPAComplianceManager()
gpc_handler = GPCHandler(ccpa_manager)
 
@app.route('/do-not-sell', methods=['GET'])
def do_not_sell_page():
    """Render Do Not Sell My Personal Information page."""
    gpc_status = gpc_handler.check_gpc_signal(dict(request.headers))
 
    return render_template('do-not-sell.html', {
        'gpc_detected': gpc_status,
        'categories_sold': get_categories_sold(),
        'third_parties': get_third_party_recipients()
    })
 
@app.route('/api/privacy/opt-out', methods=['POST'])
def opt_out_api():
    """API endpoint for opt-out requests."""
    data = request.json
 
    consumer_id = data.get('consumer_id') or request.cookies.get('consumer_id')
 
    if not consumer_id:
        consumer_id = generate_anonymous_id()
        set_anonymous_opt_out(consumer_id)
 
    ccpa_request = ccpa_manager.submit_request(
        consumer_id=consumer_id,
        right_type=CCPARight.OPT_OUT,
        verification_method="web_form"
    )
 
    response = jsonify({
        'success': True,
        'request_id': ccpa_request.request_id,
        'status': ccpa_request.status.value
    })
 
    response.set_cookie('ccpa_opt_out', '1', max_age=31536000, secure=True, httponly=True)
 
    return response
 
@app.route('/api/privacy/gpc', methods=['POST'])
def gpc_api():
    """Handle GPC signal notification from client."""
    user_context = {
        'consumer_id': request.cookies.get('consumer_id'),
        'session_id': request.cookies.get('session_id')
    }
 
    result = gpc_handler.process_gpc_request(dict(request.headers), user_context)
 
    return jsonify(result)
 
@app.route('/api/privacy/request', methods=['POST'])
def privacy_request_api():
    """Submit CCPA privacy request."""
    data = request.json
 
    right_type = CCPARight(data.get('right_type', 'right_to_know'))
 
    categories = [
        PersonalInfoCategory(cat) for cat in data.get('categories', [])
    ] or None
 
    ccpa_request = ccpa_manager.submit_request(
        consumer_id=data['email'],
        right_type=right_type,
        categories=categories,
        verification_method=data.get('verification_method', 'email')
    )
 
    return jsonify({
        'request_id': ccpa_request.request_id,
        'status': ccpa_request.status.value,
        'deadline': ccpa_request.deadline.isoformat(),
        'verification_required': True
    })
 
@app.route('/api/privacy/verify', methods=['POST'])
def verify_request():
    """Verify consumer identity for request."""
    data = request.json
 
    success = ccpa_manager.verify_consumer(
        request_id=data['request_id'],
        verification_code=data['verification_code']
    )
 
    if success:
        ccpa_request = ccpa_manager.requests[data['request_id']]
        return jsonify({
            'verified': True,
            'status': ccpa_request.status.value
        })
 
    return jsonify({'verified': False, 'error': 'Invalid verification code'}), 400
 
def get_categories_sold():
    """Get list of PI categories that are sold/shared."""
    return [
        {
            'category': PersonalInfoCategory.INTERNET_ACTIVITY.value,
            'description': 'Browsing and search history',
            'recipients': ['Advertising partners', 'Analytics providers']
        }
    ]
 
def get_third_party_recipients():
    """Get list of third parties receiving PI."""
    return [
        {'name': 'Advertising Network A', 'purpose': 'Targeted advertising'},
        {'name': 'Analytics Provider B', 'purpose': 'Website analytics'}
    ]
 
def generate_anonymous_id():
    import uuid
    return f"anon_{uuid.uuid4().hex}"
 
def set_anonymous_opt_out(consumer_id):
    ccpa_manager.opted_out_consumers.add(consumer_id)

Privacy Notice Generator

Generate required privacy disclosures:

class PrivacyNoticeGenerator:
    """Generate CCPA-compliant privacy notices."""
 
    def __init__(self, ccpa_manager: CCPAComplianceManager):
        self.ccpa_manager = ccpa_manager
 
    def generate_privacy_policy_section(self) -> Dict:
        """Generate CCPA section for privacy policy."""
        return {
            'title': 'California Privacy Rights (CCPA)',
            'sections': [
                {
                    'heading': 'Categories of Personal Information Collected',
                    'content': self._get_categories_collected()
                },
                {
                    'heading': 'Sources of Personal Information',
                    'content': self._get_sources()
                },
                {
                    'heading': 'Business Purposes for Collection',
                    'content': self._get_business_purposes()
                },
                {
                    'heading': 'Categories Sold or Shared',
                    'content': self._get_categories_sold()
                },
                {
                    'heading': 'Your Rights Under CCPA',
                    'content': self._get_consumer_rights()
                },
                {
                    'heading': 'How to Exercise Your Rights',
                    'content': self._get_exercise_rights()
                },
                {
                    'heading': 'Non-Discrimination',
                    'content': "We will not discriminate against you for exercising your CCPA rights."
                }
            ]
        }
 
    def _get_categories_collected(self) -> List[Dict]:
        categories = []
        for category, entries in self.ccpa_manager.data_inventory.items():
            if entries:
                categories.append({
                    'category': category.value,
                    'description': self.ccpa_manager._get_category_description(category),
                    'collected': True
                })
        return categories
 
    def _get_sources(self) -> List[str]:
        sources = set()
        for entries in self.ccpa_manager.data_inventory.values():
            for entry in entries:
                sources.add(entry.source)
        return list(sources)
 
    def _get_business_purposes(self) -> List[str]:
        purposes = set()
        for entries in self.ccpa_manager.data_inventory.values():
            for entry in entries:
                purposes.add(entry.business_purpose)
        return list(purposes)
 
    def _get_categories_sold(self) -> List[Dict]:
        sold = []
        for category, entries in self.ccpa_manager.data_inventory.items():
            for entry in entries:
                if entry.sold_or_shared:
                    sold.append({
                        'category': category.value,
                        'recipients': entry.third_parties_shared
                    })
        return sold
 
    def _get_consumer_rights(self) -> List[Dict]:
        return [
            {'right': 'Right to Know', 'description': 'Request disclosure of personal information collected'},
            {'right': 'Right to Delete', 'description': 'Request deletion of personal information'},
            {'right': 'Right to Opt-Out', 'description': 'Opt-out of sale or sharing of personal information'},
            {'right': 'Right to Correct', 'description': 'Request correction of inaccurate information'},
            {'right': 'Right to Limit Use', 'description': 'Limit use of sensitive personal information'}
        ]
 
    def _get_exercise_rights(self) -> str:
        return """
        You may exercise your rights by:
        - Visiting our "Do Not Sell My Personal Information" page
        - Emailing privacy@company.com
        - Calling our toll-free number: 1-800-XXX-XXXX
        - Using our online privacy request form
 
        We will verify your identity before processing your request.
        """

Conclusion

CCPA compliance requires systematic technical implementation of consumer rights, data inventory management, and opt-out mechanisms. Build automated request handling systems, implement GPC detection, and maintain accurate records of data processing activities. Regular audits and employee training ensure ongoing compliance with California's privacy requirements.

Weekly AI Security & Automation Digest

Get the latest on AI Security, workflow automation, secure integrations, and custom platform development delivered weekly.

No spam. Unsubscribe anytime.