Voltar ao blog
Technical

The Privacy Sandbox: Google’s Vision for a Private Web

Equipa GetCookiesNovember 7, 202516 min de leitura
Privacy SandboxGoogleChromeFuture

TLDR: Google delayed cookie deprecation again—but Privacy Sandbox APIs are production-ready now. Companies testing Topics and Protected Audience are learning while competitors wait. When the hammer finally falls, preparation will separate survivors from victims.

Read full summary

Technical overview of Privacy Sandbox APIs: Topics, Protected Audience, Attribution Reporting, and more. Learn the timeline, browser support status, and practical migration strategies from third-party cookies.

Summary by Claude AI

The Companies Who Waited (and the Ones Who Didn't)

When Google first announced third-party cookie deprecation in 2020, the digital advertising industry had three years to prepare. Then came extensions. Delays. The CMA investigation. More delays.

Many companies treated each delay as validation of their "wait and see" strategy. They kept building on third-party cookies, assuming they'd have warning before any changes hit.

In 2024, Google began testing cookie deprecation with 1% of Chrome users. The companies who had been experimenting with Privacy Sandbox APIs—Topics for interest-based targeting, Protected Audience for remarketing, Attribution Reporting for measurement—saw their preparation pay off. Their campaigns continued working.

The companies who had waited scrambled. They discovered that Privacy Sandbox APIs work differently. That Topics provides only 5 interest categories per week. That Protected Audience requires entirely new auction infrastructure. That Attribution Reporting has 30-day windows for view-through attribution.

The full deprecation timeline remains uncertain, but the APIs are stable. The question isn't whether to prepare—it's whether you can afford not to.


title: "The Future of Google's Privacy Sandbox: Complete Technical Guide 2025" slug: "future-of-google-privacy-sandbox-guide" excerpt: "Deep dive into Google's Privacy Sandbox APIs including Topics, Protected Audience, and Attribution Reporting. Learn how these technologies work, their consent requirements, and how to prepare your advertising strategy for a cookieless future." category: "Privacy Technology" tags: ["Privacy Sandbox", "Topics API", "Protected Audience", "FLEDGE", "Attribution Reporting", "Third-Party Cookies"] publishedAt: "2025-01-20" readTime: "24 min read"

The Future of Google's Privacy Sandbox: Complete Technical Guide

Google's Privacy Sandbox represents the most significant shift in digital advertising since the introduction of third-party cookies. While Chrome's complete deprecation timeline has evolved, the Privacy Sandbox APIs are now production-ready and being actively used by advertisers worldwide. This comprehensive guide explains how each API works, what consent requirements apply, and how to implement these technologies in your advertising stack.

Understanding the Privacy Sandbox Vision

The Privacy Sandbox isn't a single technology—it's a collection of APIs designed to enable advertising use cases without cross-site tracking. The fundamental principle is simple: keep user data on the device, process it locally, and only share aggregated or anonymized information with advertisers.

Why Third-Party Cookies Are Problematic

Third-party cookies enable tracking users across websites, building detailed profiles without their knowledge:

// Traditional cross-site tracking (being deprecated)
// Site A sets a cookie
document.cookie = "tracker_id=abc123; domain=.tracker.com; path=/";

// Site B reads the same cookie
// The tracker now knows the same user visited both sites
fetch('https://tracker.com/collect', {
    credentials: 'include',  // Sends the cookie
    body: JSON.stringify({
        page: window.location.href,
        referrer: document.referrer
    })
});

This pattern allowed ad networks to build comprehensive user profiles across the entire web. Privacy Sandbox replaces this with purpose-built APIs that achieve similar advertising outcomes while keeping individual user data private.

Topics API: Interest-Based Advertising Without Tracking

The Topics API enables interest-based advertising by having the browser itself determine user interests based on browsing history, then sharing only high-level topic categories with advertisers.

How Topics Works

// Topics API architecture
// 1. Browser observes sites visited
// 2. Browser categorizes sites into topics (from ~350 topics taxonomy)
// 3. Each week, browser selects top 5 topics for the user
// 4. One random topic added to prevent fingerprinting
// 5. Topics shared with callers are filtered by observed topics

// Checking if Topics API is available
if ('browsingTopics' in document) {
    console.log('Topics API is supported');
}

// Requesting topics (requires user opted-in)
async function getTopicsForAds() {
    try {
        const topics = await document.browsingTopics();
        console.log('User topics:', topics);
        // Example response:
        // [
        //   { topic: 57, configVersion: "chrome.1", taxonomyVersion: "1", modelVersion: "1" },
        //   { topic: 126, configVersion: "chrome.1", taxonomyVersion: "1", modelVersion: "1" }
        // ]
        return topics;
    } catch (error) {
        // Topics not available (user opted out, not enough browsing history, etc.)
        console.log('Topics unavailable:', error);
        return [];
    }
}

Topics Taxonomy

The Topics taxonomy includes approximately 350 interest categories organized hierarchically:

Topic ID Topic Name Parent
1 Arts & Entertainment Root
57 Sports Root
58 Team Sports Sports
126 Shopping Root
127 Apparel Shopping
200 Technology Root
250 Travel Root
300 Finance Root

Implementing Topics for Ad Targeting

// Complete Topics API implementation for ad server
class TopicsAdTargeting {
    private static TAXONOMY_VERSION = '1';
    private static MIN_TOPICS_FOR_TARGETING = 1;

    // Topic to ad category mapping
    private static topicToAdCategory: Map<number, string[]> = new Map([
        [57, ['sports_equipment', 'fitness', 'sports_apparel']],
        [126, ['retail', 'ecommerce', 'deals']],
        [200, ['electronics', 'software', 'gadgets']],
        [250, ['flights', 'hotels', 'vacation_packages']],
        [300, ['banking', 'investments', 'insurance']],
    ]);

    async getTargetingSignals(): Promise<TopicsTargetingResult> {
        // Check API availability
        if (!('browsingTopics' in document)) {
            return {
                available: false,
                topics: [],
                adCategories: [],
                reason: 'API not supported'
            };
        }

        try {
            const topics = await document.browsingTopics({
                skipObservation: false  // Include this page in topic calculation
            });

            if (topics.length < TopicsAdTargeting.MIN_TOPICS_FOR_TARGETING) {
                return {
                    available: false,
                    topics: [],
                    adCategories: [],
                    reason: 'Insufficient topics'
                };
            }

            // Map topics to ad categories
            const adCategories = new Set<string>();
            for (const topic of topics) {
                const categories = TopicsAdTargeting.topicToAdCategory.get(topic.topic);
                if (categories) {
                    categories.forEach(cat => adCategories.add(cat));
                }
            }

            return {
                available: true,
                topics: topics.map(t => t.topic),
                adCategories: Array.from(adCategories),
                reason: null
            };
        } catch (error) {
            return {
                available: false,
                topics: [],
                adCategories: [],
                reason: error.message
            };
        }
    }

    // Server-side topic processing via headers
    setupTopicsHeaders(): void {
        // Topics can also be received via HTTP headers
        // Request header: Sec-Browsing-Topics
        // Response header: Observe-Browsing-Topics: ?1

        // In your ad server, check for the header:
        // const topics = request.headers['sec-browsing-topics'];
    }
}

interface TopicsTargetingResult {
    available: boolean;
    topics: number[];
    adCategories: string[];
    reason: string | null;
}

Topics Consent Requirements

Importantly, Topics API still requires user consent under GDPR:

// Topics consent integration
class TopicsConsentManager {
    private consentGranted: boolean = false;

    async checkConsent(): Promise<boolean> {
        // Check CMP for Topics consent
        return new Promise((resolve) => {
            if (typeof window.__tcfapi === 'function') {
                window.__tcfapi('getTCData', 2, (tcData, success) => {
                    if (success) {
                        // Topics requires Purpose 1 (storage/access) at minimum
                        // Many interpret it as also requiring Purpose 3 (ad profiles)
                        const hasConsent = tcData.purpose.consents[1] &&
                                          (tcData.purpose.consents[3] || tcData.purpose.legitimateInterests[3]);
                        this.consentGranted = hasConsent;
                        resolve(hasConsent);
                    } else {
                        resolve(false);
                    }
                });
            } else {
                // No TCF CMP, check for simpler consent
                resolve(this.checkSimpleConsent());
            }
        });
    }

    private checkSimpleConsent(): boolean {
        const consent = this.getConsentCookie();
        return consent?.analytics === true || consent?.marketing === true;
    }

    async getTopicsWithConsent(): Promise<number[]> {
        const hasConsent = await this.checkConsent();

        if (!hasConsent) {
            console.log('Topics blocked: no consent');
            return [];
        }

        try {
            const topics = await document.browsingTopics();
            return topics.map(t => t.topic);
        } catch {
            return [];
        }
    }
}

Protected Audience API (formerly FLEDGE): Remarketing Without Tracking

The Protected Audience API enables remarketing and custom audiences without sharing user browsing data across sites. Interest groups are stored in the browser, and ad auctions happen locally.

How Protected Audience Works

// Protected Audience flow:
// 1. Advertiser adds user to interest group on their site
// 2. User visits publisher site
// 3. Publisher initiates on-device ad auction
// 4. Browser runs auction using stored interest groups
// 5. Winning ad is displayed without revealing user data

// Step 1: Join an interest group (on advertiser site)
async function joinInterestGroup() {
    const interestGroup = {
        owner: 'https://dsp.example.com',
        name: 'running-shoes-viewed',

        // Bidding logic loaded at auction time
        biddingLogicUrl: 'https://dsp.example.com/bidding.js',
        biddingWasmHelperUrl: 'https://dsp.example.com/bidding.wasm',

        // Update URL for refreshing group data
        updateUrl: 'https://dsp.example.com/update/running-shoes',

        // Trusted server for real-time signals
        trustedBiddingSignalsUrl: 'https://dsp.example.com/signals',
        trustedBiddingSignalsKeys: ['running-shoes', 'q4-promo'],

        // User signals stored locally
        userBiddingSignals: {
            productViewed: 'running-shoes-xyz',
            viewedAt: Date.now(),
            pricePoint: 149.99
        },

        // Ad creatives
        ads: [
            {
                renderUrl: 'https://cdn.example.com/ads/running-shoes-1.html',
                metadata: { campaign: 'holiday-sale', discount: '20%' }
            },
            {
                renderUrl: 'https://cdn.example.com/ads/running-shoes-2.html',
                metadata: { campaign: 'new-arrival' }
            }
        ],

        // Component ads for multi-seller auctions
        adComponents: [
            { renderUrl: 'https://cdn.example.com/components/logo.html' },
            { renderUrl: 'https://cdn.example.com/components/cta.html' }
        ]
    };

    try {
        await navigator.joinAdInterestGroup(interestGroup, 30 * 24 * 60 * 60);  // 30 days
        console.log('Successfully joined interest group');
    } catch (error) {
        console.error('Failed to join interest group:', error);
    }
}

// Step 2: Run auction on publisher site
async function runProtectedAudienceAuction() {
    const auctionConfig = {
        seller: 'https://ssp.publisher.com',
        decisionLogicUrl: 'https://ssp.publisher.com/decision.js',

        // Who can participate
        interestGroupBuyers: [
            'https://dsp.example.com',
            'https://another-dsp.com'
        ],

        // Signals available to all bidders
        auctionSignals: {
            pageContext: 'sports-article',
            adFormat: 'banner-300x250'
        },

        // Per-buyer configurations
        perBuyerSignals: {
            'https://dsp.example.com': {
                customData: 'premium-placement'
            }
        },

        // Trusted scoring signals
        trustedScoringSignalsUrl: 'https://ssp.publisher.com/scoring-signals',

        // Component auctions for header bidding
        componentAuctions: [
            {
                seller: 'https://exchange1.com',
                decisionLogicUrl: 'https://exchange1.com/decision.js',
                interestGroupBuyers: ['https://buyer1.com']
            }
        ]
    };

    try {
        const adAuctionResult = await navigator.runAdAuction(auctionConfig);

        if (adAuctionResult) {
            // Render winning ad in fenced frame
            const fencedFrame = document.createElement('fencedframe');
            fencedFrame.config = adAuctionResult;
            document.getElementById('ad-slot').appendChild(fencedFrame);
        } else {
            // No winner, show contextual ad
            showContextualAd();
        }
    } catch (error) {
        console.error('Auction failed:', error);
        showContextualAd();
    }
}

Bidding Logic Implementation

// bidding.js - runs in browser during auction
function generateBid(interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals) {
    // Calculate bid based on interest group data
    const basePrice = interestGroup.userBiddingSignals.pricePoint || 100;
    const daysSinceView = (Date.now() - interestGroup.userBiddingSignals.viewedAt) / (1000 * 60 * 60 * 24);

    // Decay bid value over time
    let bidMultiplier = Math.max(0.1, 1 - (daysSinceView * 0.05));

    // Adjust based on trusted signals
    if (trustedBiddingSignals && trustedBiddingSignals['q4-promo']) {
        bidMultiplier *= 1.5;  // Increase bid for promotional period
    }

    // Consider page context
    if (auctionSignals.pageContext === 'sports-article') {
        bidMultiplier *= 1.2;  // Higher bid for relevant content
    }

    const bidValue = basePrice * bidMultiplier * 0.01;  // CPM calculation

    // Select best ad creative
    const selectedAd = selectBestAd(interestGroup.ads, auctionSignals);

    return {
        bid: bidValue,
        render: selectedAd.renderUrl,
        adComponents: [interestGroup.adComponents[0].renderUrl],
        allowComponentAuction: true
    };
}

function selectBestAd(ads, signals) {
    // Simple selection logic
    const promoAd = ads.find(ad => ad.metadata.discount);
    if (promoAd && signals.pageContext !== 'checkout') {
        return promoAd;
    }
    return ads[0];
}

function reportWin(auctionSignals, perBuyerSignals, sellerSignals, browserSignals) {
    // Report win for billing/analytics
    sendReportTo('https://dsp.example.com/report-win?' +
        'campaign=' + encodeURIComponent(browserSignals.interestGroupName) +
        '&bid=' + browserSignals.bid);
}

Decision Logic (Seller Side)

// decision.js - SSP's scoring logic
function scoreAd(adMetadata, bid, auctionConfig, trustedScoringSignals, browserSignals) {
    // Validate ad
    if (!isAdAllowed(adMetadata, auctionConfig)) {
        return 0;  // Reject ad
    }

    // Check brand safety
    if (trustedScoringSignals.blockedBrands?.includes(adMetadata.advertiser)) {
        return 0;
    }

    // Score based on bid and relevance
    let score = bid;

    // Boost for premium advertisers
    if (trustedScoringSignals.premiumBuyers?.includes(browserSignals.interestGroupOwner)) {
        score *= 1.1;
    }

    // Apply floor price
    const floorPrice = auctionConfig.auctionSignals.floorPrice || 0.5;
    if (bid < floorPrice) {
        return 0;
    }

    return score;
}

function reportResult(auctionConfig, browserSignals) {
    // Report auction result
    sendReportTo('https://ssp.publisher.com/report?' +
        'winning_bid=' + browserSignals.bid +
        '&winning_bidder=' + encodeURIComponent(browserSignals.interestGroupOwner));
}

Protected Audience Consent Requirements

// Protected Audience consent integration
class ProtectedAudienceConsentManager {
    async checkAndJoinInterestGroup(group: InterestGroup): Promise<boolean> {
        // Protected Audience requires marketing consent
        const hasConsent = await this.checkMarketingConsent();

        if (!hasConsent) {
            console.log('Cannot join interest group: no marketing consent');
            return false;
        }

        try {
            await navigator.joinAdInterestGroup(group, 30 * 24 * 60 * 60);
            return true;
        } catch (error) {
            console.error('Failed to join group:', error);
            return false;
        }
    }

    async leaveAllInterestGroups(): Promise<void> {
        // When user withdraws consent, leave all groups
        if ('leaveAdInterestGroup' in navigator) {
            // Note: Can only leave groups you own
            // Users can also clear via browser settings
            console.log('Interest group removal requested');
        }
    }

    private async checkMarketingConsent(): Promise<boolean> {
        // Check TCF consent
        return new Promise((resolve) => {
            if (typeof window.__tcfapi === 'function') {
                window.__tcfapi('getTCData', 2, (tcData, success) => {
                    if (success) {
                        // Requires Purpose 1, 3, and 4 for remarketing
                        const hasConsent = tcData.purpose.consents[1] &&
                                          tcData.purpose.consents[3] &&
                                          tcData.purpose.consents[4];
                        resolve(hasConsent);
                    } else {
                        resolve(false);
                    }
                });
            } else {
                resolve(this.checkSimpleMarketingConsent());
            }
        });
    }
}

Attribution Reporting API: Measuring Conversions Privately

The Attribution Reporting API measures ad conversions without tracking users across sites. It provides two types of reports: event-level (for optimization) and summary (for aggregate measurement).

How Attribution Reporting Works

// Attribution flow:
// 1. User clicks/views ad (source event registered)
// 2. User converts on advertiser site (trigger event registered)
// 3. Browser matches source to trigger
// 4. Report generated with privacy protections

// Step 1: Register ad click as attribution source
function registerAttributionSource() {
    // Method 1: HTML anchor with attribution
    const adLink = document.createElement('a');
    adLink.href = 'https://advertiser.com/landing';
    adLink.attributionSrc = 'https://adtech.com/register-source';
    adLink.textContent = 'Shop Now';

    // Method 2: JavaScript registration
    // Requires Permissions-Policy: attribution-reporting=*
    if ('attributionReporting' in window) {
        fetch('https://adtech.com/register-impression', {
            attributionReporting: {
                eventSourceEligible: true,
                triggerEligible: false
            }
        });
    }
}

// Server response to register source
// HTTP/1.1 200 OK
// Attribution-Reporting-Register-Source: {
//   "destination": "https://advertiser.com",
//   "source_event_id": "12345678901234567",
//   "expiry": "604800",
//   "priority": "100",
//   "debug_key": "debug123",
//   "aggregation_keys": {
//     "campaignCounts": "0x159",
//     "geoValue": "0x5"
//   },
//   "filter_data": {
//     "product_id": ["shoes-123"],
//     "category": ["sports"]
//   }
// }

Trigger Registration (Conversion Side)

// Step 2: Register conversion on advertiser site
async function registerConversion(conversionData) {
    // Check for attribution source in query params or cookies
    // Then register the trigger

    // Method 1: Pixel/image
    const pixel = document.createElement('img');
    pixel.src = 'https://adtech.com/conversion?value=' + conversionData.value;
    pixel.attributionSrc = '';  // Empty triggers header-based registration

    // Method 2: Fetch API
    await fetch('https://adtech.com/register-trigger', {
        method: 'POST',
        body: JSON.stringify(conversionData),
        attributionReporting: {
            eventSourceEligible: false,
            triggerEligible: true
        }
    });
}

// Server response to register trigger
// HTTP/1.1 200 OK
// Attribution-Reporting-Register-Trigger: {
//   "event_trigger_data": [{
//     "trigger_data": "3",
//     "priority": "100",
//     "deduplication_key": "dedupe123",
//     "filters": {
//       "product_id": ["shoes-123"]
//     }
//   }],
//   "aggregatable_trigger_data": [{
//     "key_piece": "0x400",
//     "source_keys": ["campaignCounts"]
//   }],
//   "aggregatable_values": {
//     "campaignCounts": 32768
//   },
//   "debug_key": "debug456"
// }

Complete Attribution Implementation

// Full attribution reporting implementation
class PrivacySandboxAttribution {
    private adTechOrigin: string;

    constructor(origin: string) {
        this.adTechOrigin = origin;
    }

    // Register ad impression/click as source
    async registerSource(sourceData: SourceRegistration): Promise<void> {
        const headers: HeadersInit = {
            'Content-Type': 'application/json',
        };

        // Build source registration payload
        const sourceConfig = {
            destination: sourceData.advertiserDomain,
            source_event_id: sourceData.eventId,
            expiry: sourceData.expirySeconds || 2592000,  // 30 days default
            priority: sourceData.priority || 0,
            event_report_window: sourceData.eventReportWindow,
            aggregatable_report_window: sourceData.aggregatableReportWindow,
            aggregation_keys: sourceData.aggregationKeys,
            filter_data: sourceData.filterData,
            debug_reporting: sourceData.debugMode
        };

        try {
            await fetch(`${this.adTechOrigin}/register-source`, {
                method: 'POST',
                headers,
                body: JSON.stringify(sourceConfig),
                // @ts-ignore - Attribution reporting fetch option
                attributionReporting: {
                    eventSourceEligible: true,
                    triggerEligible: false
                }
            });
        } catch (error) {
            console.error('Source registration failed:', error);
        }
    }

    // Register conversion as trigger
    async registerTrigger(triggerData: TriggerRegistration): Promise<void> {
        const triggerConfig = {
            event_trigger_data: [{
                trigger_data: triggerData.conversionType,
                priority: triggerData.priority || 0,
                deduplication_key: triggerData.deduplicationKey,
                filters: triggerData.filters
            }],
            aggregatable_trigger_data: triggerData.aggregatableTriggerData,
            aggregatable_values: triggerData.aggregatableValues,
            debug_reporting: triggerData.debugMode
        };

        try {
            await fetch(`${this.adTechOrigin}/register-trigger`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(triggerConfig),
                // @ts-ignore
                attributionReporting: {
                    eventSourceEligible: false,
                    triggerEligible: true
                }
            });
        } catch (error) {
            console.error('Trigger registration failed:', error);
        }
    }

    // Process received reports (server-side)
    processEventLevelReport(report: EventLevelReport): ConversionData {
        return {
            sourceEventId: report.source_event_id,
            triggerData: report.trigger_data,  // Limited to 3 bits (8 values)
            reportId: report.report_id,
            sourceSite: report.source_origin,
            destinationSite: report.destination,
            scheduledReportTime: new Date(report.scheduled_report_time * 1000),
            // Note: No user identifier, no precise timing
        };
    }

    processAggregateReport(report: AggregateReport): AggregateData {
        // Aggregate reports contain encrypted, noisy data
        // Must be processed by aggregation service
        return {
            reportId: report.report_id,
            sharedInfo: JSON.parse(report.shared_info),
            aggregationServicePayloads: report.aggregation_service_payloads,
            // Decrypted on aggregation service with differential privacy
        };
    }
}

interface SourceRegistration {
    advertiserDomain: string;
    eventId: string;
    expirySeconds?: number;
    priority?: number;
    eventReportWindow?: number;
    aggregatableReportWindow?: number;
    aggregationKeys?: Record<string, string>;
    filterData?: Record<string, string[]>;
    debugMode?: boolean;
}

interface TriggerRegistration {
    conversionType: string;
    priority?: number;
    deduplicationKey?: string;
    filters?: Record<string, string[]>;
    aggregatableTriggerData?: Array<{
        key_piece: string;
        source_keys: string[];
    }>;
    aggregatableValues?: Record<string, number>;
    debugMode?: boolean;
}

Attribution Consent Requirements

// Attribution API consent handling
class AttributionConsentManager {
    async canUseAttribution(): Promise<boolean> {
        // Attribution Reporting requires:
        // - Purpose 1: Store/access information on device
        // - Purpose 7: Measure ad performance
        // - Purpose 10: Develop and improve products (for aggregate reports)

        return new Promise((resolve) => {
            if (typeof window.__tcfapi === 'function') {
                window.__tcfapi('getTCData', 2, (tcData, success) => {
                    if (success && tcData.gdprApplies) {
                        const hasConsent =
                            tcData.purpose.consents[1] &&
                            tcData.purpose.consents[7];
                        resolve(hasConsent);
                    } else if (success && !tcData.gdprApplies) {
                        resolve(true);  // Non-EU user
                    } else {
                        resolve(false);
                    }
                });
            } else {
                // Check simple consent
                const consent = this.getConsentCookie();
                resolve(consent?.analytics === true);
            }
        });
    }

    // Integrate with attribution registration
    async registerSourceWithConsent(data: SourceRegistration): Promise<void> {
        const canUse = await this.canUseAttribution();

        if (!canUse) {
            console.log('Attribution blocked: no consent for measurement');
            return;
        }

        const attribution = new PrivacySandboxAttribution('https://adtech.example.com');
        await attribution.registerSource(data);
    }
}

Private Aggregation API: Cross-Site Measurement

The Private Aggregation API enables aggregate measurement across sites with differential privacy protections.

// Private Aggregation usage within Protected Audience
function generateBid(interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals) {
    // ... bidding logic ...

    // Contribute to aggregate histogram
    privateAggregation.contributeToHistogram({
        bucket: BigInt(interestGroup.userBiddingSignals.productCategory),
        value: 1
    });

    return { bid: 1.50, render: 'https://cdn.example.com/ad.html' };
}

function reportWin(auctionSignals, perBuyerSignals, sellerSignals, browserSignals) {
    // Record win in aggregate report
    privateAggregation.contributeToHistogram({
        bucket: BigInt('0x' + hashCampaignId(browserSignals.interestGroupName)),
        value: Math.floor(browserSignals.bid * 1000)  // Record bid in millicents
    });
}

Shared Storage API: Limited Cross-Site Storage

Shared Storage allows limited cross-site data storage with restricted output gates:

// Writing to shared storage (any context)
await window.sharedStorage.set('user-segment', 'high-value');
await window.sharedStorage.append('viewed-products', ',product-123');

// Reading via output gates only
// 1. URL Selection (for A/B testing)
const frameUrl = await window.sharedStorage.selectURL(
    'ab-testing',
    [
        { url: 'https://cdn.example.com/variant-a.html' },
        { url: 'https://cdn.example.com/variant-b.html' }
    ],
    {
        data: { experimentId: 'exp-001' },
        resolveToConfig: true  // For fenced frames
    }
);

// 2. Private Aggregation (for measurement)
class MeasurementOperation {
    async run(data) {
        const segment = await sharedStorage.get('user-segment');
        privateAggregation.contributeToHistogram({
            bucket: BigInt(data.campaignId),
            value: segment === 'high-value' ? 100 : 10
        });
    }
}
register('measurement', MeasurementOperation);

Implementing a Complete Privacy Sandbox Strategy

Migration Timeline and Approach

// Privacy Sandbox migration strategy
class PrivacySandboxMigration {
    private features = {
        topics: false,
        protectedAudience: false,
        attribution: false,
        sharedStorage: false
    };

    async initialize(): Promise<void> {
        // Detect available features
        this.features.topics = 'browsingTopics' in document;
        this.features.protectedAudience = 'joinAdInterestGroup' in navigator;
        this.features.attribution = 'attributionReporting' in window;
        this.features.sharedStorage = 'sharedStorage' in window;

        console.log('Privacy Sandbox features:', this.features);
    }

    async getTargetingStrategy(): Promise<TargetingStrategy> {
        const strategy: TargetingStrategy = {
            primary: 'contextual',
            fallbacks: []
        };

        // Check consent first
        const consent = await this.checkConsent();

        if (consent.marketing && this.features.protectedAudience) {
            strategy.primary = 'protected-audience';
            strategy.fallbacks.push('topics', 'contextual');
        } else if (consent.analytics && this.features.topics) {
            strategy.primary = 'topics';
            strategy.fallbacks.push('contextual');
        }

        return strategy;
    }

    async runHybridAuction(): Promise<AdResult> {
        const strategy = await this.getTargetingStrategy();

        // Try Privacy Sandbox first
        if (strategy.primary === 'protected-audience') {
            const paResult = await this.runProtectedAudienceAuction();
            if (paResult) return paResult;
        }

        // Fall back to Topics-enhanced contextual
        if (strategy.primary === 'topics' || strategy.fallbacks.includes('topics')) {
            const topics = await this.getTopics();
            if (topics.length > 0) {
                return await this.runContextualAuctionWithTopics(topics);
            }
        }

        // Pure contextual fallback
        return await this.runContextualAuction();
    }

    async setupMeasurement(): Promise<void> {
        const consent = await this.checkConsent();

        if (consent.analytics && this.features.attribution) {
            // Use Privacy Sandbox attribution
            this.setupAttributionReporting();
        } else if (consent.analytics) {
            // Fall back to first-party analytics
            this.setupFirstPartyAnalytics();
        } else {
            // Consent-less aggregated analytics only
            this.setupAggregateOnlyAnalytics();
        }
    }
}

Testing and Debugging

// Privacy Sandbox testing utilities
const PrivacySandboxTesting = {
    // Check API availability
    checkAPIs() {
        return {
            topics: {
                available: 'browsingTopics' in document,
                permissionPolicy: document.featurePolicy?.allowsFeature('browsing-topics')
            },
            protectedAudience: {
                available: 'joinAdInterestGroup' in navigator,
                permissionPolicy: document.featurePolicy?.allowsFeature('join-ad-interest-group')
            },
            attribution: {
                available: 'attributionReporting' in window,
                permissionPolicy: document.featurePolicy?.allowsFeature('attribution-reporting')
            },
            sharedStorage: {
                available: 'sharedStorage' in window,
                permissionPolicy: document.featurePolicy?.allowsFeature('shared-storage')
            },
            fencedFrames: {
                available: 'HTMLFencedFrameElement' in window
            },
            privateAggregation: {
                available: 'privateAggregation' in window
            }
        };
    },

    // Debug Topics
    async debugTopics() {
        try {
            const topics = await document.browsingTopics();
            console.table(topics);
            return topics;
        } catch (e) {
            console.error('Topics error:', e);
            return [];
        }
    },

    // Debug interest groups
    async debugInterestGroups() {
        // Use chrome://topics-internals
        // Use chrome://interest-group-internals
        console.log('Visit chrome://interest-group-internals for debugging');
    },

    // Test attribution flow
    async testAttribution() {
        // Check for pending reports
        // chrome://attribution-internals
        console.log('Visit chrome://attribution-internals for debugging');
    }
};

Preparing Your CMPs for Privacy Sandbox

CMPs need to understand that Privacy Sandbox doesn't eliminate consent requirements:

// CMP Privacy Sandbox integration
class CMPPrivacySandboxIntegration {
    // Map TCF purposes to Privacy Sandbox APIs
    private purposeMapping = {
        topics: [1, 3],           // Storage + Ad profiles
        protectedAudience: [1, 3, 4],  // Storage + Profiles + Personalized ads
        attribution: [1, 7],      // Storage + Measurement
        sharedStorage: [1],       // Storage
        privateAggregation: [1, 7, 10]  // Storage + Measurement + Development
    };

    canUseAPI(api: string, tcfConsent: TCFData): boolean {
        const requiredPurposes = this.purposeMapping[api] || [];

        return requiredPurposes.every(purpose =>
            tcfConsent.purpose.consents[purpose] ||
            tcfConsent.purpose.legitimateInterests[purpose]
        );
    }

    getAvailableAPIs(tcfConsent: TCFData): string[] {
        return Object.keys(this.purposeMapping).filter(api =>
            this.canUseAPI(api, tcfConsent)
        );
    }

    // Update consent mode based on Privacy Sandbox usage
    updateGoogleConsentMode(tcfConsent: TCFData): void {
        const canUseTopics = this.canUseAPI('topics', tcfConsent);
        const canUsePA = this.canUseAPI('protectedAudience', tcfConsent);
        const canUseAttribution = this.canUseAPI('attribution', tcfConsent);

        window.dataLayer.push({
            event: 'consent_update',
            // Google Consent Mode v2 signals
            ad_storage: canUsePA ? 'granted' : 'denied',
            ad_user_data: canUseTopics || canUsePA ? 'granted' : 'denied',
            ad_personalization: canUsePA ? 'granted' : 'denied',
            analytics_storage: canUseAttribution ? 'granted' : 'denied'
        });
    }
}

The Road Ahead

The Privacy Sandbox represents a fundamental shift in how digital advertising works. While the APIs are more complex than dropping a third-party cookie, they enable sustainable advertising that respects user privacy.

Key takeaways:

  1. Privacy Sandbox doesn't eliminate consent - GDPR still requires user consent to access these browser APIs
  2. Start testing now - All major APIs are available in Chrome stable
  3. Plan for hybrid - You'll need fallbacks for non-Chrome browsers and users who opt out
  4. Update measurement - Attribution Reporting provides less granular data; adjust expectations
  5. Invest in first-party data - Privacy Sandbox works best when combined with strong first-party relationships

The future of digital advertising is privacy-preserving by design. Organizations that embrace Privacy Sandbox now will be best positioned when third-party cookies finally disappear from Chrome. Those that wait will face a scramble to maintain advertising effectiveness.

G

Equipa GetCookies

Escrito e mantido pela equipa GetCookies da Getia AS em Oslo, Noruega. Este artigo é informação geral sobre consentimento de cookies, não aconselhamento jurídico.

Analise o seu próprio site grátis

O plano gratuito mostra o banner num site com três análises de cookies por mês. Sem cartão, sem chamadas comerciais.