Recording an opt-out preference without actually stopping data sharing is a CCPA § 1798.120 violation more egregious than having no opt-out at all — it deceives the consumer into believing their request was honored. The most common pattern: opt-out cookie is set correctly, suppressing one pixel (e.g., Facebook), while server-side Segment track() calls or a Google Analytics identify() still fire unconditionally because they were wired into API route handlers that never check the opt-out flag. Each page load where a third-party sharing call fires for an opted-out consumer is a separate violation. This check requires tracing the enforcement gap, not just the preference storage.
Medium because the gap between stored opt-out preference and actual enforcement of that preference across all sharing touchpoints is a concrete CCPA § 1798.120 violation per sharing event, even when the opt-out UI exists.
Create a centralized isUserOptedOut() helper that both client-side pixels and server-side analytics calls must pass through. Enumerate every third-party sharing touchpoint and confirm each one is gated.
// lib/analytics.ts — single source of truth for opt-out check
import { cookies } from 'next/headers'
export async function isUserOptedOut(userId?: string): Promise<boolean> {
if (userId) {
const pref = await db.privacyPreference.findFirst({ where: { userId, type: 'sale_sharing' } })
if (pref) return true
}
const cookieStore = cookies()
return cookieStore.get('ccpa_opt_out')?.value === '1'
}
// Server-side event — every analytics call uses this wrapper
export async function trackServerEvent(event: string, props: Record<string, unknown>, userId?: string) {
if (await isUserOptedOut(userId)) return
await segment.track({ event, properties: props, userId })
}
Search src/ for segment.track, gtag, fbq, analytics.identify, and mixpanel.track calls and confirm each one is wrapped or gated. Document the gating coverage in PRIVACY_OPERATIONS.md.
ID: ccpa-readiness.opt-out.third-party-sharing-gated
Severity: medium
What to look for: This check specifically verifies that the opt-out preference is enforced at the code level — that third-party data sharing actually stops, not just that an opt-out preference is recorded. Trace the execution path: when a page loads with an active opt-out preference (cookie or server-side flag), do advertising pixels (Facebook, Google Ads, TikTok) still fire? Check client-side analytics initialization code: does it read the opt-out cookie before loading pixel scripts? Check server-side data flows: are there any server-side API calls that send user data to third parties (data brokers, advertising platforms, enrichment services) that are not gated on the opt-out flag? Check webhook handlers: if user events are piped to Segment, Amplitude, or similar, are those calls suppressed when the user is opted out? Enumerate all third-party sharing touchpoints: list all pixel scripts, server-side event calls, and webhook handlers that transmit PI. For each, classify as gated or ungated on opt-out preference.
Pass criteria: All third-party sharing pathways — client-side pixels, server-side API calls to advertising platforms, and analytics identify/track calls — are gated on the consumer's opt-out status. When an opt-out is active, no personal information is shared with third parties for advertising or sharing purposes. Report even on pass: "X of Y third-party sharing pathways confirmed gated on opt-out preference. All advertising pixels suppressed when opt-out active." At least 1 implementation must be confirmed.
Fail criteria: Opt-out cookie is set but one or more third-party sharing pathways continue to fire. Server-side data flows to advertising or analytics platforms are not gated on opt-out status. Analytics identify() calls fire unconditionally regardless of opt-out state.
Skip (N/A) when: Application has no third-party data sharing — no advertising pixels, no behavioral analytics that share PI, no data broker integrations.
Detail on fail: Example: "Opt-out cookie correctly suppresses Facebook Pixel but Google Analytics identify() call fires unconditionally in src/app/providers.tsx regardless of opt-out state." or "Server-side Segment track() calls in API route handlers are not gated on user's opt-out preference.".
Remediation: Audit every data-sharing touchpoint and add opt-out gating:
// lib/analytics.ts — centralized opt-out check for all sharing
import { cookies } from 'next/headers'
// Server-side: check opt-out from cookie or database
export async function isUserOptedOut(userId?: string): Promise<boolean> {
if (userId) {
const pref = await db.privacyPreference.findFirst({
where: { userId, type: 'sale_sharing' }
})
if (pref) return true
}
const cookieStore = cookies()
return cookieStore.get('ccpa_opt_out')?.value === '1'
}
// Server-side event tracking — gated
export async function trackServerEvent(
event: string,
properties: Record<string, unknown>,
userId?: string
) {
if (await isUserOptedOut(userId)) return
await segment.track({ event, properties, userId })
}
// Client-side — gate all pixel firing
export function trackClientEvent(event: string, properties: Record<string, unknown>) {
const optedOut = document.cookie.includes('ccpa_opt_out=1') ||
navigator.globalPrivacyControl === true
if (optedOut) return
fbq?.('track', event, properties)
gtag?.('event', event, properties)
}