CCPA § 1798.105 gives every California consumer — not just account holders — the right to have their personal information deleted. Deletion must be real: soft deletes that flip a deleted_at flag while leaving the record intact violate the statute. Worse, requiring an account to submit a deletion request is explicitly prohibited; that barrier alone is a CCPA violation independent of whether deletion actually works. Data retained past its legal basis is also a data-breach liability — hard-deleted records cannot be exfiltrated. Regulators treat deletion request failures as evidence of a systemic compliance gap, escalating enforcement from warnings to fines.
Critical because blocking or ignoring deletion requests is a direct CCPA § 1798.105 violation, and soft-delete implementations create ongoing data-breach exposure for data that should no longer exist.
Implement an unauthenticated deletion request endpoint that issues a verification token via email, then permanently removes or anonymizes the consumer's records — not a soft-delete — once verified. The route must be reachable without login.
// app/api/privacy/request/route.ts — unauthenticated deletion flow
import { randomBytes } from 'crypto'
export async function POST(req: Request) {
const { requestType, email } = await req.json()
const token = randomBytes(32).toString('hex')
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000)
await db.privacyRequest.create({
data: { type: requestType, email, verificationToken: token, expiresAt, status: 'pending_verification' }
})
await sendEmail({
to: email,
subject: 'Verify your California Privacy Rights request',
body: `Verify here: https://yourdomain.com/privacy/verify?token=${token}`,
})
return Response.json({ ok: true })
}
On verification, run permanent deletion across all tables — not UPDATE users SET deleted_at = NOW(). Document the permitted CCPA deletion exceptions (active transaction, fraud prevention, legal obligation) in your fulfillment runbook so staff can apply them consistently.
ID: ccpa-readiness.consumer-rights.right-to-delete
Severity: critical
What to look for: Check for a deletion request mechanism — either as a separate option on the privacy rights request form or as a "Delete Account" feature in user settings. For CCPA, deletion rights extend to consumers who are not account holders; the mechanism must be accessible without requiring account creation. Verify that the deletion workflow includes identity verification (at minimum, a confirmation link sent to the email provided). Check whether deletion actually removes or anonymizes personal information across all systems — database, third-party services, backups. Look for any documented exceptions to deletion (e.g., data needed to complete a transaction, detect fraud, comply with legal obligations) — CCPA permits withholding deletion where exceptions apply, but exceptions must be disclosed. Check for a two-step confirmation to prevent accidental deletion. Count all instances found and enumerate each.
Pass criteria: A deletion request mechanism exists that is accessible without account creation. Identity verification is required before deletion is processed. Personal information is actually deleted (or anonymized) from the primary database. Documented exceptions to deletion are disclosed. The consumer receives confirmation that deletion was completed or that an exception applies. At least 1 implementation must be confirmed.
Fail criteria: No deletion request mechanism exists. Deletion requires account creation (impermissible barrier). No identity verification before deletion is processed. Deletion only marks data as deleted without actual removal. Do NOT pass if the only deletion mechanism requires account creation — CCPA explicitly prohibits this barrier.
Skip (N/A) when: Same CCPA threshold analysis as right-to-know — document if skipping.
Cross-reference: The retention-limits check in Data Handling verifies that disclosed retention periods align with actual deletion enforcement.
Detail on fail: Specify the issue. Example: "No deletion request mechanism found outside of account deletion for authenticated users. Non-account-holders have no way to submit a deletion request." or "Deletion request form exists but requires creating an account before submitting — this is an impermissible barrier under CCPA." or "Account deletion sets deleted_at flag only; no permanent data removal confirmed.".
Remediation: Implement an unauthenticated deletion request flow with identity verification:
// app/api/privacy/request/route.ts
import { randomBytes } from 'crypto'
export async function POST(req: Request) {
const { requestType, email } = await req.json()
// Generate a verification token
const token = randomBytes(32).toString('hex')
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
// Store the pending request
await db.privacyRequest.create({
data: {
type: requestType,
email,
verificationToken: token,
expiresAt,
status: 'pending_verification',
}
})
// Send verification email
await sendEmail({
to: email,
subject: 'Verify your California Privacy Rights request',
body: `Click to verify your deletion request: https://example.com/privacy/verify?token=${token}`,
})
return Response.json({ ok: true })
}
// app/api/privacy/verify/route.ts — called when user clicks verification link
export async function GET(req: Request) {
const token = new URL(req.url).searchParams.get('token')
const request = await db.privacyRequest.findFirst({
where: { verificationToken: token, expiresAt: { gt: new Date() } }
})
if (!request) return Response.json({ error: 'Invalid or expired token' }, { status: 400 })
// Queue the request for fulfillment within 45 days
await db.privacyRequest.update({
where: { id: request.id },
data: { status: 'verified', verifiedAt: new Date() }
})
return Response.json({ ok: true, message: 'Request verified. We will respond within 45 days.' })
}