| name | kvkk-compliance |
| description | KVKK and GDPR compliance patterns - consent management, right to erasure, breach notification, audit logging, cookie consent, and data classification. |
KVKK & GDPR Compliance Patterns
Practical patterns for Turkish KVKK (Law No. 6698) and EU GDPR data protection compliance.
KVKK vs GDPR Comparison
| Aspect | KVKK (Turkey) | GDPR (EU) |
|---|
| Authority | KVKK Board (Kisisel Verileri Koruma Kurumu) | National DPAs + EDPB |
| Consent | Explicit, no pre-ticked boxes | Freely given, specific, informed |
| Breach notification | "As soon as possible" to Board | 72 hours to DPA |
| DPO requirement | VERBiS registration | Mandatory for public bodies + large-scale |
| Right to erasure | Article 7 - withdrawal + deletion | Article 17 - "Right to be forgotten" |
| Data transfer abroad | Board approval or adequate country | Adequacy decision, SCCs, or BCRs |
| Fines | Up to ~2M TL per violation | Up to 20M EUR or 4% global turnover |
| Legal bases | 5 in Article 5 + explicit consent | 6 in Article 6 + explicit consent |
Data Classification
enum DataCategory {
PERSONAL = 'personal',
SPECIAL = 'special_category',
ANONYMOUS = 'anonymous',
PSEUDONYMOUS = 'pseudonymous',
}
interface DataField {
name: string
category: DataCategory
retentionDays: number
requiresExplicitConsent: boolean
encryptAtRest: boolean
}
const USER_DATA_FIELDS: DataField[] = [
{ name: 'email', category: DataCategory.PERSONAL, retentionDays: 730, requiresExplicitConsent: false, encryptAtRest: false },
{ name: 'healthRecords', category: DataCategory.SPECIAL, retentionDays: 365, requiresExplicitConsent: true, encryptAtRest: true },
{ name: 'analyticsId', category: DataCategory.PSEUDONYMOUS, retentionDays: 365, requiresExplicitConsent: false, encryptAtRest: false },
]
Consent Management
interface ConsentRecord {
userId: string; purpose: string; granted: boolean
timestamp: Date; ipAddress: string; version: string
}
const CONSENT_PURPOSES = [
{ id: 'essential', label: 'Service operation', required: true },
{ id: 'analytics', label: 'Usage analytics', required: false },
{ id: 'marketing', label: 'Marketing emails', required: false },
{ id: 'third_party', label: 'Third-party sharing', required: false },
] as const
async function recordConsent(userId: string, purposeId: string, : , : { ip: ; userAgent: }): <> {
db..({
: { userId, : purposeId, granted, : (), : meta., : },
})
}
(): <> {
latest = db..({
: { userId, : purposeId },
: { : },
})
latest?. ===
}
Right to Erasure (Soft Delete + Anonymization)
const GRACE_PERIOD_DAYS = 30
async function requestAccountDeletion(userId: string): Promise<void> {
await db.users.update({ where: { id: userId }, data: { status: 'deletion_pending', deactivatedAt: new Date() } })
await db.deletionRequests.create({
data: { userId, requestedAt: new Date(), gracePeriodEndsAt: new Date(Date.now() + GRACE_PERIOD_DAYS * 86400000), status: 'pending' },
})
}
async function purgeExpiredAccounts(): Promise<void> {
const expired = await db.deletionRequests.findMany({
where: { status: 'pending', gracePeriodEndsAt: { : () } },
})
( req expired) {
db.$transaction( (tx) => {
tx..({
: { : req. },
: { : , : , : , : , : },
})
tx..({
: { : req. },
: { : , : }
})
tx..({ : { : req. }, : { : , : } })
tx..({ : { : req. }, : { : , : () } })
})
}
}
Cookie Consent Flow
const COOKIE_CATEGORIES = {
necessary: { name: 'Strictly Necessary', required: true },
functional: { name: 'Functional', required: false },
analytics: { name: 'Analytics', required: false },
marketing: { name: 'Marketing', required: false },
} as const
type CookiePrefs = Record<keyof typeof COOKIE_CATEGORIES, boolean>
function applyCookiePreferences(prefs: CookiePrefs): void {
initSessionCookies()
prefs.analytics ? initGoogleAnalytics() : removeAnalyticsCookies()
prefs.marketing ? initMarketingPixels() : removeMarketingCookies()
document.cookie = `cookie_consent=; path=/; max-age=; SameSite=Lax; Secure`
}
Data Breach Notification (72-Hour Rule)
const NOTIFICATION_DEADLINE_HOURS = 72
async function reportBreach(breach: { detectedAt: Date; severity: string; affectedCount: number; dataTypes: string[]; description: string }): Promise<void> {
const record = await db.dataBreaches.create({ data: { ...breach, notifiedAuthorityAt: null, notifiedUsersAt: null } })
const deadline = new Date(breach.detectedAt.getTime() + NOTIFICATION_DEADLINE_HOURS * 3600000)
await notifyDataProtectionAuthority({ breachId: record.id, deadline, ...breach })
if (breach.severity === 'high' || breach.severity === 'critical') {
await notifyAffectedUsers(record.id)
}
}
Audit Logging (Who, What, When, Which Data)
interface AuditLogEntry {
actorId: string; actorRole: string
action: 'read' | 'create' | 'update' | 'delete' | 'export'
resourceType: string; resourceId: string
fieldsAccessed: string[]; ipAddress: string
justification?: string
}
async function logDataAccess(entry: AuditLogEntry): Promise<void> {
await db.$executeRaw`
INSERT INTO audit_logs (actor_id, actor_role, action, resource_type, resource_id, fields_accessed, ip_address, justification, timestamp)
VALUES (${entry.actorId}, ${entry.actorRole}, ${entry.action}, ${entry.resourceType}, ${entry.resourceId}, ${JSON.stringify(entry.fieldsAccessed)}, ${entry.ipAddress}, ${entry.justification || null}, NOW())
`
}
function auditMiddleware(resourceType: ) {
{
origJson = res..(res)
res. = {
: <, > = {
: , : , : , : , :
}
({
: req.?. || ,
: req.?. || ,
: actionMap[req.] || ,
resourceType,
: req.. || ,
: .((body <, >) || {}),
: req. || ,
}).(.)
(body)
}
()
}
}
Privacy Policy Checklist
Controller identity [ ] Company name, address, contact, VERBiS number
Data collected [ ] Full list of categories with examples
Legal basis [ ] Purpose + legal basis for each activity
Retention periods [ ] How long each type is kept
Data subject rights [ ] Access, rectification, erasure, portability, objection
Consent withdrawal [ ] Clear opt-out instructions
Cookie policy [ ] Categories, purposes, opt-out
International transfer [ ] Countries, safeguards (SCCs, adequacy)
Third parties [ ] Processors and sub-processors
Automated decisions [ ] Profiling details, right to object
Breach procedure [ ] Notification timeline and method
DPO contact [ ] Data Protection Officer details
DPA Template Reference
Every third-party processor needs a Data Processing Agreement with these clauses:
Subject & duration What data, how long, why
Processor obligations Process only on documented instructions
Sub-processors Prior written auth, flow-down obligations
Security measures Encryption, access controls, incident response
Audit rights Controller can inspect compliance
Data return/deletion Return or destroy data at contract end
Breach notification Notify controller without undue delay
International transfer SCCs if data leaves TR/EU
Contrast: GOOD vs BAD Consent
function BadForm() {
return (
<form>
<label><input type="checkbox" /> I agree to privacy policy, marketing, tracking, and sharing.</label>
<button>Sign Up</button>
</form>
)
}
function GoodForm() {
return (
<form>
<fieldset>
<legend>Data Processing Consent</legend>
<label><input type="checkbox" checked disabled /> Account operation (required)</label>
<label><input type="checkbox" name="analytics" /> Usage analytics</label>
<label>< = = /> Marketing emails
Partner sharing (details)
Change preferences anytime in account settings.
Sign Up
)
}
Core rule: Compliance is not a one-time feature. Build data protection into every flow, log every access, and assume regulators will ask "show me the evidence."