
HR ve İK Platformlarında KVKK Kabusu ve Veribenim Çözümü: Aday CV'leri, İşe Alım Verisi ve Otomatik Anonimleştirme
HR ve İK Platformlarında KVKK Kabusu ve Veribenim Çözümü: Aday CV'leri, İşe Alım Verisi ve Otomatik Anonimleştirme
Giriş: ATS (Applicant Tracking System) + KVKK = Zaman Bombası
İşinizi düşünün. Bir ATS (Applicant Tracking System) platformu kullanıyorsunuz. Her gün yüzlerce, hatta binlerce başvuru geliyor. Her başvurunun içinde:
Özgeçmiş (CV): Adı, soyadı, telefonu, e-postası, eğitim ve iş deneyimi
Kapı yazısı (Cover Letter): Kişisel hedefler, motivasyonlar
Sosyal Medya Linkleri: LinkedIn profili, portfolyo siteleri
Fotoğraf: Birçok başvuruda CV'ye iliştirilen fotoğraf
Sağlık Bilgileri: Engelli işe alım kotası, sağlık yapısı
Eğitim Belgeleri: Diploma, sertifika taramaları
Referans Bilgileri: Eski işverenler, telefon numaraları
Peki, bunları ne kadar tutabilirsıniz?
KVKK kapsamında, bu veriler "özel nitelikli kişisel veriler" kategorisinedir. Türkiye'de işe alım süreci KVKK Madde 7 ve GDPR Madde 5(1)(e) altında ciddi bir kompliansı mekanizması gerektirir. Çoğu HR platformu bunu göz ardı eder — ve bu, yargıya kadar gidebilir.
Bu makale, HR ve İK platformlarının nasıl KVKK uyumlu hale gelebileceğini, Veribenim SDK'ları ile otomatik anonimleştirme ve veri yönetimi yapacağını teknik derinlikte anlatır.
1. CV'de Hangi Veriler "Özel Nitelikli" Sayılır?
KVKK Madde 6'ya göre, aşağıdaki veriler "hassas kişisel veriler" kategorisindedir:
Veri Türü | KVKK Tanımı | Yönetim Zorluk Seviyesi |
|---|---|---|
Fotoğraf | Kişinin fiziki özelliklerini ortaya koyan görsel veri. Yüz tanıma kapsamında biyometrik. | 🔴 Çok Yüksek |
Sağlık Bilgisi | Engelli işe alım, kronik hastalık, tıbbi durum, nedeniyle. | 🔴 Çok Yüksek |
Etnik Köken | CV'de kimliğe işaret eden referans. | 🔴 Çok Yüksek |
Sendika Üyeliği | İnsan kaynakları formu alanında yer alan. | 🟠 Yüksek |
Politik Görüş | Sosyal medya linkleri üzerinden tersiyle. | 🟠 Yüksek |
E-posta & Telefon | Doğrudan kimlik. | 🟡 Orta |
Eğitim Tarihi | İndirekt yaş tahmini. | 🟡 Orta |
Sosyal Medya Profili | KVKK Madde 22 kapsamında "açık kaynak" ama rıza gerekli. | 🟡 Orta |
Ad-Soyad | Kişinin hemen tanınması. | 🟢 Temel |
⚖️ HUKUKİ NOT: KVKK Madde 6 kapsamında, "özel nitelikli kişisel veri" işlemek için açık rıza ve meşru hedef (işe alım için işe alım hedefi) gereklidir. GDPR'da bu "Special Categories of Personal Data" (Madde 9) olarak adlandırılır.
2. KVKK'ya Göre CV Saklama Süresi: 1 Yıl mı, 2 Yıl mı, Süresiz mi?
İşte kritik soru: "Bu CV'yi ne kadar tutmalıyım?"
KVKK Madde 7(1) açık cevap veriyor: Veriler "yalnızca meşru hedef için gerekli olduğu süre" saklanabilir.
İşe alım bağlamında:
✅ İşe Alınan Aday
İşe Alım Sonrası Veriler
├─ İş İlişkisi Süresi (yasal)
│ └─ Bordro, SSK, Vergi Müfettişliği
│
└─ İş İlişkisi BİTİŞ Sonrası
├─ 10 yıl (İş Kanunu Madde 53 - belge saklaması)
│ └─ Bordro, yıllık izin, tazminat kayıtları
│
└─ Ancak CV ÖZÜ (fotoğraf, sağlık, sosyal medya) → ANONİMLEŞTİR
❌ İşe Alınmayan Aday
Başvurmuş Ama Seçilmemiş
├─ Saklama Süresi: 1-2 Yıl
│ (KVKK Rehberi: "Olası ilerideki açılışlar için")
│
└─ 2 Yıl Dolunca
├─ CV TAMAMEN AYNI SİL veya ANONİMLEŞTİR
└─ Hukuki yükümlülük biterse de → İyi uygulama
💡 PRO TIP: Çoğu HR platformu "başvuruyu arşivde tut" düşüncesiyle CV'yi süresiz saklıyor. Bu KVKK ihlalidir. Veribenim ile otomatik olarak saklama süresi hesaplanır ve zamanlayıcı job süresi bitince anonimleştirme yapılır.
3. Sosyal Medya Araştırması: LinkedIn Profili KVKK İhlali Riski
Modern HR ekipleri linkedin.com üzerinde ek araştırma yapıyor. Bu tehlikeli.
🔴 Risk Senaryoları
Senaryo 1: Aday Koşulsuz Görünüş
HR: "Bu aday harika görünüyor. Ama LinkedIn'de fotoğrafının yanında
'Müslüman İş Kadınları Kulübü' üyesi yazıyor. Din önyargısı oluştursa?"
→ KVKK Madde 6 İhlaline Gider
Senaryo 2: Yaş Tahmini
HR: "LinkedIn'de başlandı 1995. Bu bize 29-30 yaşında olduğunu söylüyor.
Genç oyun ekibi istiyoruz, daha yaşlı birine almaycağız."
→ GDPR Article 5 + KVKK Madde 5 (Meşru hedef yok)
Senaryo 3: Gizli İş Mülakat
HR: "Bu aday X şirkette İnsan Kaynakları Müdürü. Ülkede sadece 3 tane var.
İdentitesi garantili. Stalkılamış."
→ Data Sovereignty İhlali (KVKK Madde 8)
✅ KVKK Uyumlu Sosyal Medya Kontrol
Eğer gerekirse sosyal medya araştırması yapacaksanız:
Rıza Al: "LinkedIn profilinizi kontrol etmek istiyoruz" yazılarında açık olun
Objektif Kriterler: Yalnızca "teknik beceri kanıtı" ara. Din, cinsiyet, yaş çıkarımı yapma
İzole Et: Sosyal medya bulguları ayrı dosyada tut. CV'ye bağlantı kurma
Süre Sınırı: Sosyal medya araştırması sonrası bulgularını 30 gün içinde sil
⚖️ HUKUKİ NOT: GDPR Madde 22'de "sadece otomatik karar" hakkı vardır. İnsan + Makine Hybrid (HR meraklı + LinkedIn scraper bot) hukuki boşluk oluşturur. Veribenim çözümü: Sosyal medya araştırması loglanır, 30 gün sonra otomatik silinir.
4. İşe Alınmayan Adaylar için Saklama Süresi Farkı
Türk HR hukuku bu konuda tasvizdir: İş Kanunu ve KVKK farklı süreler söylüyor.
📊 Saklama Süresi Tablosu
Durum | KVKK Görüş | İş Kanunu | Uygulanan | Sebep |
|---|---|---|---|---|
İşe alınan aday | İş ilişkisi + 10 yıl | 10 yıl (Md. 53) | 10 yıl | Vergi, SSK, tazminat |
Red edilen aday | 1-2 yıl (ilerideki pozisyonlar) | N/A | 2 yıl | KVKK meşru hedef |
Arşiv (müdür kararı) | Açık kaydında işareti gerekli | N/A | 2 yıl maks | Kanuni güvenlik |
Yeniden başvuranlar | Her başvuru için sürü sıfırlanır | N/A | 2 yıl | KVKK meşru hedef yenilenme |
💡 PRO TIP: Veribenim otomatik saklama süresi izler. Türk hukuku için: İşe alınan = 10 yıl, Red edilen = 2 yıl. Sistem takvime göre tüm alan anonimleştirmeyi otomatik yapar.
5. Nasıl Entegre Edilir? — PHP SDK ile Otomatik Anonimleştirme
Şimdi çözüm. Veribenim PHP SDK ile başvuru formundan anonimleştirmeye kadar nasıl otomatik akış yaratılır:
Adım 1: Başvuru Formu Consent Loglama
<?php
// /app/Services/ApplicationFormService.php
use Veribenim\\VeribenimClient;
use Illuminate\\Http\\UploadedFile;
class ApplicationFormService
{
private VeribenimClient $veribenim;
public function __construct()
{
$this->veribenim = new VeribenimClient(config('veribenim.token'));
}
/**
* İş başvuru formundan gelen veriye consent işle
*
* @param array $formData [full_name, email, phone, position, cv_has_photo]
* @param UploadedFile $cvFile
* @return array ['success' => bool, 'application_id' => int|null, 'error' => string|null]
*/
public function processApplication(array $formData, UploadedFile $cvFile): array
{
// 1. Zorunlu Consent Kontrolü
if (empty($formData['kvkk_consent'])) {
return [
'success' => false,
'error' => 'KVKK onayı zorunludur. İş başvurusunu tamamlayamazsınız.'
];
}
// 2. CV'de Fotoğraf Var mı? Uyarı Logla
$cvHasPhoto = $this->detectPhotoInCv($cvFile);
if ($cvHasPhoto) {
// Fotoğraflı CV -> Ayrı consent gerekli
if (empty($formData['photo_consent'])) {
return [
'success' => false,
'error' => 'CV\\'nize fotoğraf eklemişsiniz. Fotoğraf işlenmesi için ayrı onay gereklidir.'
];
}
}
// 3. Consent Log (KVKK Madde 5 - İzlenebilirlik)
$consentLog = $this->veribenim->logFormConsent(
formName: 'job_application',
consented: true,
consentText: sprintf(
'Başvurduğum %s pozisyonu için verdiğim başvuru kapsamında ' .
'kişisel verilerimin (ad, soyad, e-posta, telefon, özgeçmiş, eğitim ve iş deneyimi) ' .
'yönetim amaçlı olarak %d yıl süreyle işlenmesini onaylıyorum. ' .
'KVKK Madde 6 ve Aydınlatma Metni kapsamında onay veriyorum. ' .
'%s',
$formData['position'],
2, // 2 yıl saklama süresi
$cvHasPhoto ? 'CV\\'mde yer alan fotoğrafın da işlenmesini onaylıyorum.' : ''
),
metadata: [
'position' => $formData['position'],
'application_date' => now()->toDateString(),
'cv_uploaded' => true,
'cv_has_photo' => $cvHasPhoto,
'phone_provided' => !empty($formData['phone']),
'linkedin_provided' => !empty($formData['linkedin_url']),
'applicant_country' => $this->detectCountry($formData['email']),
]
);
if (!$consentLog || empty($consentLog['id'])) {
\\Log::error('[Veribenim] Consent log başarısız', $consentLog);
return [
'success' => false,
'error' => 'Sistem hatası. Lütfen daha sonra tekrar deneyin.'
];
}
// 4. CV'yi Şifreli Depola (Zero-Knowledge)
$cvPath = $this->storeCvEncrypted($cvFile, $formData['email']);
if (!$cvPath) {
return [
'success' => false,
'error' => 'CV yüklenemedi. Lütfen daha sonra deneyin.'
];
}
// 5. Başvuru Kaydını Oluştur (KVKK Anonimleştirme Tarihi ile)
$application = Application::create([
'company_id' => auth('company')->id(),
'applicant_full_name' => $formData['full_name'],
'applicant_email' => $formData['email'],
'applicant_phone' => $formData['phone'] ?? null,
'position' => $formData['position'],
'cv_path' => $cvPath,
'cv_has_photo' => $cvHasPhoto,
'linkedin_url' => $formData['linkedin_url'] ?? null,
'consent_id' => $consentLog['id'],
'consent_logged_at' => now(),
'retention_until' => now()->addYears(2), // KVKK Compliance
'status' => 'pending',
'applied_at' => now(),
]);
// 6. İleri Araştırma Kural Sınırı Yaz (Sosyal Medya Araştırması)
if (!empty($formData['linkedin_url'])) {
$application->metadata()->create([
'key' => 'linkedin_research_deadline',
'value' => now()->addDays(30)->toIso8601String(),
]);
}
\\Log::info('[Veribenim HR] Yeni başvuru işlendi', [
'application_id' => $application->id,
'position' => $application->position,
'retention_until' => $application->retention_until,
'consent_id' => $consentLog['id'],
]);
return [
'success' => true,
'application_id' => $application->id,
'message' => 'Başvurunuz alındı. 2-3 iş günü içinde geri dönüş yapacağız.'
];
}
/**
* CV'de fotoğraf olup olmadığını algıla
* PDDocument kullanarak basit kontrol
*/
private function detectPhotoInCv(UploadedFile $file): bool
{
if ($file->getClientMimeType() !== 'application/pdf') {
// PDF değilse, DOCX ise Word metadatasından kontrol
return false; // Basitleştirilmiş
}
try {
$pdf = \\PDFParser\\Parser::parse($file->getContent());
// PDF'de image nesnesi varsa → fotoğraf var
return $pdf->getPages()[0]->countImages() > 0;
} catch (\\Exception $e) {
return false;
}
}
/**
* CV dosyasını şifreli depola (Zero-Knowledge Storage)
*/
private function storeCvEncrypted(UploadedFile $file, string $email): ?string
{
$filename = sprintf(
'cv_%s_%s.%s',
hash('sha256', $email),
now()->timestamp,
$file->getClientOriginalExtension()
);
// Encryptable storage
$path = $file->storeAs(
'cvs/encrypted',
$filename,
disk: 'encrypted'
);
return $path ?: null;
}
private function detectCountry(string $email): string
{
// Basit: tr domaininden mi
return str_ends_with($email, '.tr') ? 'TR' : 'OTHER';
}
}
Adım 2: Otomatik CV Anonimleştirme Job
<?php
// /app/Jobs/CvAnonymizationJob.php
namespace App\\Jobs;
use App\\Models\\Application;
use Veribenim\\VeribenimClient;
use Illuminate\\Bus\\Queueable;
use Illuminate\\Queue\\SerializesModels;
class CvAnonymizationJob implements ShouldQueue
{
use Queueable, SerializesModels;
private VeribenimClient $veribenim;
public function __construct()
{
$this->veribenim = new VeribenimClient(config('veribenim.token'));
}
/**
* Her gün çalışacak (Scheduler tarafından tetiklenir)
* Retention süresi dolmuş CV'leri anonimleştir
*/
public function handle(): void
{
\\Log::info('[CvAnonymizationJob] Başlıyor...');
// 1. RESİ ALILMAMIŞ ADAYLAR → 2 YIL SONRA
$this->anonymizeRejectedApplications();
// 2. İŞE ALINANLAR → 10 YIL SONRA (İS İLİŞKİSİ BİTİŞ SONRASI)
$this->anonymizeTerminatedEmployeeRecords();
// 3. SOSYAL MEDYA ARAŞTIRMASI → 30 GÜN SONRA
$this->clearLinkedInResearchData();
\\Log::info('[CvAnonymizationJob] Tamamlandı.');
}
/**
* Red edilen adayların CV'sini 2 yıl sonra sil
*/
private function anonymizeRejectedApplications(): void
{
$expiredApplications = Application::where('retention_until', '<', now())
->whereIn('status', ['rejected', 'withdrawn', 'pending']) // İşe alınmamış
->whereNull('anonymized_at')
->get();
\\Log::info("[CvAnonymizationJob] Red edilen/Bekleyen başvurular: {$expiredApplications->count()} adet");
foreach ($expiredApplications as $application) {
try {
$this->anonymizeApplication(
$application,
reason: 'KVKK Madde 7(1) - Meşru hedef sona erdi (2 yıl retention)',
isEmployee: false
);
} catch (\\Exception $e) {
\\Log::error("[CvAnonymizationJob] Başvuru #{$application->id} anonimleştirmede hata", [
'error' => $e->getMessage()
]);
}
}
}
/**
* İşten ayrılan çalışanların CV'sini 10 yıl sonra sil
*/
private function anonymizeTerminatedEmployeeRecords(): void
{
$expiredEmployees = Application::where('status', 'hired')
->whereNotNull('employee_termination_date')
->whereRaw('DATE_ADD(employee_termination_date, INTERVAL 10 YEAR) < NOW()')
->whereNull('anonymized_at')
->get();
\\Log::info("[CvAnonymizationJob] Emekli çalışanlar: {$expiredEmployees->count()} adet");
foreach ($expiredEmployees as $record) {
try {
$this->anonymizeApplication(
$record,
reason: 'İş Kanunu Md. 53 - 10 yıl saklama süresi doldu',
isEmployee: true
);
} catch (\\Exception $e) {
\\Log::error("[CvAnonymizationJob] Çalışan #{$record->id} anonimleştirmede hata", [
'error' => $e->getMessage()
]);
}
}
}
/**
* LinkedIn araştırması verilerini 30 gün sonra sil
* (GDPR Right to Erasure Madde 17)
*/
private function clearLinkedInResearchData(): void
{
$linkedinDataToDelete = Application::whereHas('metadata', function ($q) {
$q->where('key', 'linkedin_research_deadline')
->whereRaw('CAST(value AS DATE) < NOW()');
})->whereNull('linkedin_cleared_at')->get();
foreach ($linkedinDataToDelete as $application) {
$application->update([
'linkedin_url' => null, // Sil
'linkedin_cleared_at' => now(),
]);
$application->metadata()
->where('key', 'linkedin_research_deadline')
->delete();
\\Log::info("[CvAnonymizationJob] LinkedIn verisi silindi - Başvuru #{$application->id}");
}
}
/**
* Ana anonimleştirme fonksiyonu
*/
private function anonymizeApplication(
Application $app,
string $reason,
bool $isEmployee = false
): void {
// 1. CV DİSKİ SİL
if ($app->cv_path && \\Storage::disk('encrypted')->exists($app->cv_path)) {
\\Storage::disk('encrypted')->delete($app->cv_path);
\\Log::info("[Veribenim HR] CV dosyası silindi - App #{$app->id}");
}
// 2. KİŞİSEL ALANLARI ANONİMLEŞTİR
$anonymousSeed = hash('sha256', $app->id . config('app.key'));
$anonymousId = substr($anonymousSeed, 0, 8);
$app->update([
'applicant_full_name' => "ANONİM ADAY #{$anonymousId}",
'applicant_email' => null,
'applicant_phone' => null,
'linkedin_url' => null,
'cv_path' => null,
'cv_has_photo' => false,
'anonymized_at' => now(),
'anonymized_reason' => $reason,
]);
// 3. VERİBENİM ÜZERİNDEN DSAR KAYıT
// (Veribenim Dashboard'da "Veri Silme Tarihçesi" görünsün)
$this->veribenim->submitDsar(
requestType: 'erasure',
fullName: 'System Automated',
email: 'system@' . config('app.domain'),
description: sprintf(
'Otomatik Anonimleştirme Tamamlandı\\n' .
'- Başvuru ID: %d\\n' .
'- Pozisyon: %s\\n' .
'- Durum: %s\\n' .
'- İşe Alındı: %s\\n' .
'- Sebep: %s\\n' .
'- KVKK Madde 7(1) / GDPR Article 5(1)(e) uyumlu',
$app->id,
$app->position,
$app->status,
$isEmployee ? 'Evet' : 'Hayır',
$reason
),
metadata: [
'automation' => 'true',
'job_name' => self::class,
'application_id' => (string) $app->id,
]
);
\\Log::info("[Veribenim HR] Anonimleştirme tamamlandı - App #{$app->id}", [
'reason' => $reason,
'is_employee' => $isEmployee,
'dsar_submitted' => true,
]);
}
}
Adım 3: Scheduler Konfigürasyonu
<?php
// /app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Günlük anonimleştirme kontrol
$schedule->job(new CvAnonymizationJob)
->dailyAt('02:00') // Gece 02:00, database trafiği düşük
->onOneServer(); // Tekrar çalışma riskini kaldır
// Opsiyonel: Haftalık rapor
$schedule->command('hr:anonymization-report')
->weeklyOn(1, '09:00') // Pazartesi 09:00
->sendOutputTo(storage_path('logs/anonymization-report.log'));
}
6. Laravel SDK ile ATS Entegrasyonu
Eğer ATS (Taleo, Workable, Lever vb.) ile entegrasyon yapıyorsanız:
<?php
// /app/Services/AtsSyncService.php
// Harici ATS -> Veribenim Consent Manager
use Veribenim\\Laravel\\VeribenimFacade as Veribenim;
class AtsSyncService
{
/**
* Harici ATS'den gelen başvuru verisini Veribenim'e senkronize et
*/
public function syncApplicationFromAts(array $atsData): void
{
// ATS'de "başvuru onayı var" mi?
if (empty($atsData['consent_given'])) {
throw new \\Exception('ATS başvurusunda consent kaydı yok. Senkronizasyon iptal.');
}
// Veribenim consent log oluştur
Veribenim::logFormConsent(
formName: 'ats_imported_application',
consented: true,
consentText: sprintf(
'ATS systemi üzerinden alınan başvuru. ' .
'Orijinal rıza: %s. ' .
'Pozisyon: %s',
$atsData['consent_given'],
$atsData['position']
),
metadata: [
'ats_application_id' => $atsData['id'],
'ats_system' => $atsData['ats_name'], // 'taleo', 'workable'
'synced_at' => now()->toIso8601String(),
]
);
}
}
7. React ile İş Başvuru Formu (Frontend)
Frontend tarafında, consent UI bileşeni:
// components/JobApplicationForm.tsx
import React, { useState } from 'react';
import { useVeribenim } from '@veribenim/react';
interface JobApplicationFormProps {
position: string;
companyName: string;
onSuccess?: (applicationId: number) => void;
}
export function JobApplicationForm({
position,
companyName,
onSuccess,
}: JobApplicationFormProps) {
const { savePreferences } = useVeribenim();
const [formData, setFormData] = useState({
fullName: '',
email: '',
phone: '',
linkedinUrl: '',
cvFile: null as File | null,
kvkkConsent: false,
photoConsent: false,
});
const [cvHasPhoto, setCvHasPhoto] = useState(false);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const handleCvUpload = async (file: File) => {
setFormData((prev) => ({ ...prev, cvFile: file }));
// Basit fotoğraf algılama (PDF metadata)
if (file.type === 'application/pdf') {
const hasPhoto = await detectPhotoInPdf(file);
setCvHasPhoto(hasPhoto);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setErrors({});
// Validation
const newErrors: Record<string, string> = {};
if (!formData.fullName.trim()) {
newErrors.fullName = 'Ad-Soyad gereklidir';
}
if (!formData.email.trim()) {
newErrors.email = 'E-posta gereklidir';
}
if (!formData.cvFile) {
newErrors.cvFile = 'CV dosyası gereklidir';
}
if (!formData.kvkkConsent) {
newErrors.kvkkConsent = 'KVKK onayı zorunludur';
}
if (cvHasPhoto && !formData.photoConsent) {
newErrors.photoConsent = 'CV\\'nize fotoğraf eklenmişse, ayrı onay gereklidir';
}
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
setLoading(true);
try {
// Consent tercihleri kaydet
await savePreferences({
necessary: true,
analytics: true, // İş başvuru analitikleri
marketing: false, // İşe alım platformu pazarlama yapamaz
preferences: true,
});
// Form gönder
const formDataToSend = new FormData();
formDataToSend.append('full_name', formData.fullName);
formDataToSend.append('email', formData.email);
formDataToSend.append('phone', formData.phone);
formDataToSend.append('linkedin_url', formData.linkedinUrl);
formDataToSend.append('cv', formData.cvFile!);
formDataToSend.append('kvkk_consent', String(formData.kvkkConsent));
formDataToSend.append('photo_consent', String(formData.photoConsent));
formDataToSend.append('cv_has_photo', String(cvHasPhoto));
const response = await fetch('/api/apply', {
method: 'POST',
body: formDataToSend,
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || 'Başvuru gönderilmesinde hata');
}
onSuccess?.(result.application_id);
alert('Başvurunuz alındı! İyi şanslar.');
} catch (err) {
setErrors({
submit: err instanceof Error ? err.message : 'Bilinmeyen hata',
});
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="job-application-form">
<h2>
{position} Pozisyonuna Başvuru
</h2>
{/* Ad-Soyad */}
<div className="form-group">
<label htmlFor="fullName">
Ad-Soyad <span className="required">*</span>
</label>
<input
id="fullName"
type="text"
value={formData.fullName}
onChange={(e) => setFormData({ ...formData, fullName: e.target.value })}
aria-invalid={!!errors.fullName}
aria-describedby={errors.fullName ? 'fullName-error' : undefined}
/>
{errors.fullName && (
<p id="fullName-error" className="error">
{errors.fullName}
</p>
)}
</div>
{/* E-posta */}
<div className="form-group">
<label htmlFor="email">
E-posta <span className="required">*</span>
</label>
<input
id="email"
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && (
<p id="email-error" className="error">
{errors.email}
</p>
)}
</div>
{/* Telefon */}
<div className="form-group">
<label htmlFor="phone">
Telefon (Opsiyonel)
</label>
<input
id="phone"
type="tel"
value={formData.phone}
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
placeholder="+90 5XX XXX XXXX"
/>
</div>
{/* LinkedIn */}
<div className="form-group">
<label htmlFor="linkedinUrl">
LinkedIn Profili (Opsiyonel)
</label>
<input
id="linkedinUrl"
type="url"
value={formData.linkedinUrl}
onChange={(e) => setFormData({ ...formData, linkedinUrl: e.target.value })}
placeholder="<https://linkedin.com/in/>..."
/>
<small>
⚠️ LinkedIn araştırması KVKK kapsamında 30 gün boyunca yapılır,
sonra silinir.
</small>
</div>
{/* CV Dosyası */}
<div className="form-group">
<label htmlFor="cvFile">
Özgeçmiş (CV) <span className="required">*</span>
</label>
<input
id="cvFile"
type="file"
accept=".pdf,.doc,.docx"
onChange={(e) => handleCvUpload(e.target.files?.[0]!)}
aria-invalid={!!errors.cvFile}
aria-describedby={errors.cvFile ? 'cvFile-error' : undefined}
/>
{errors.cvFile && (
<p id="cvFile-error" className="error">
{errors.cvFile}
</p>
)}
</div>
{/* Fotoğraflı CV Uyarısı */}
{cvHasPhoto && (
<div className="alert alert-warning">
⚠️ <strong>Fotoğraflı CV Tespit Edildi</strong>
<p>
CV'nize eklediğiniz fotoğraf KVKK kapsamında "özel nitelikli kişisel veri"
sayılmaktadır. Bu başvuru için fotoğrafın işlenmesine açık onay vermeniz gereklidir.
</p>
<label>
<input
type="checkbox"
checked={formData.photoConsent}
onChange={(e) =>
setFormData({ ...formData, photoConsent: e.target.checked })
}
/>
CV'mdeki fotoğrafın {companyName} tarafından işlenmesini onaylıyorum.
</label>
{errors.photoConsent && (
<p className="error">{errors.photoConsent}</p>
)}
</div>
)}
{/* Ana KVKK Consent */}
<div className="form-group consent-section">
<label>
<input
type="checkbox"
checked={formData.kvkkConsent}
onChange={(e) => setFormData({ ...formData, kvkkConsent: e.target.checked })}
required
/>
<strong>KVKK Onayı (Zorunlu) *</strong>
</label>
<div className="consent-text">
<p>
{companyName} bünyesinde <strong>{position}</strong> pozisyonu için yaptığım
başvuru kapsamında kişisel verilerimin (ad, soyad, e-posta, telefon, eğitim,
iş deneyimi) <strong>2 yıl süreyle</strong> İnsan Kaynakları yönetimi amacıyla
işlenmesini onaylıyorum.
</p>
<ul>
<li>
<strong>Saklama Süresi:</strong> Başvuru tarihi + 2 yıl
(KVKK Madde 7)
</li>
<li>
<strong>Verinin Silinmesi:</strong> 2 yıl dolduğunda
otomatik anonimleştirme
</li>
<li>
<strong>Sosyal Medya Araştırması:</strong> LinkedIn profili
30 gün araştırıldıktan sonra silinir
</li>
</ul>
</div>
<p className="legal-note">
Daha fazla bilgi için{' '}
<a href="/kvkk-aydinlatma-metni" target="_blank" rel="noopener">
KVKK Aydınlatma Metnini
</a>
{' '}ve{' '}
<a href="/privacy-policy" target="_blank" rel="noopener">
Gizlilik Politikamızı
</a>
{' '}inceleyebilirsiniz.
</p>
{errors.kvkkConsent && (
<p className="error">{errors.kvkkConsent}</p>
)}
</div>
{/* Genel Hata */}
{errors.submit && (
<div className="alert alert-danger">
{errors.submit}
</div>
)}
{/* Gönder Butonu */}
<button
type="submit"
disabled={!formData.kvkkConsent || loading}
className="btn-primary"
>
{loading ? 'Gönderiliyor...' : 'Başvuruyu Gönder'}
</button>
</form>
);
}
// PDF'de fotoğraf algıla (Basit)
async function detectPhotoInPdf(file: File): Promise<boolean> {
try {
const arrayBuffer = await file.arrayBuffer();
// PDF'de "Image" nesnesi var mı kontrol et
const pdfText = new TextDecoder().decode(arrayBuffer);
return /\\/XObject|\\/Image|\\/DCTDecode/.test(pdfText);
} catch {
return false;
}
}
8. Data Sovereignty: Aday Verilerinin Üçüncü Taraf ATS'lere Gönderilmesi Riski
🌍 GLOBAL UYUM: KVKK Madde 8'de açık: "Kişisel veriler, yurt dışına aktarılamaz." GDPR'da Madde 44-50 kapsamında adequacy kararı gereklidir. Türkiye → USA → ATS = İHLAL.
Eğer Taleo (Oracle), Workable (EU), Lever (USA) gibi harici ATS kullanıyorsanız:
<?php
// /app/Policies/DataSovereigntyPolicy.php
class DataSovereigntyPolicy
{
/**
* Harici ATS'ye veri aktarırken kontrol
*/
public function canTransferToAts(Application $application, string $atsVendor): bool
{
// 1. ATS'nin veri merkezi konumu
$atsLocations = [
'taleo' => ['USA'], // Oracle = USA → KVKK İHLALİ
'workable' => ['EU'], // OK
'lever' => ['USA'], // KVKK İHLALİ
'freshteams' => ['EU'], // Freshdesk EU
];
$location = $atsLocations[$atsVendor] ?? null;
if (!$location || in_array('USA', $location)) {
// USA ATS = KVKK Madde 8 İhlali
\\Log::error('[DataSovereignty] USA-tabanlı ATS reddedildi', [
'ats' => $atsVendor,
'application_id' => $application->id,
]);
return false;
}
// 2. Aday consent kontrolü
if (!$application->consent_id) {
return false; // Consent yok = transfer yapma
}
return true;
}
}
⚖️ HUKUKİ NOT: Türkiye'deki HR platformları, aday verilerini sadece Avrupa ve Türkiye merkezli ATS sistemlerine gönderebilir. USA tabanlı sistemler KVKK ihlalidir. Veribenim Data Sovereignty kontrolü otomatik yapar.
9. Automated Privacy: İşe Alım Takvimi ile Otomatik Veri Döngüsü
Veribenim'in Automated Privacy özelliği ile tüm veri yaşam döngüsü otomatik:
Gün 0: Başvuru Yapıldı
↓
Consent Log + CV Şifrelenmiş Depolan
Gün 7: Seçim 1. Turda Başarısız
↓
LinkedIn 30 Gün Countdown'ı Başlat
Gün 37: LinkedIn Araştırması Süresi Doldu
↓
LinkedIn URL Sil (Otomatik)
Gün 730: CV 2 Yıl Saklama Süresi Doldu
↓
⚙️ CvAnonymizationJob Çalıştı
✓ CV Dosyası Silindi
✓ Kişisel Alanları Anonimleştirdi
✓ Veribenim DSAR Kaydı Oluşturdu
10. Verininizi Bilin (DSAR): Aday Veri Erişim/Silme Talebi
KVKK Madde 11 ve GDPR Article 15-17 altında:
<?php
// /app/Http/Controllers/ApplicantPrivacyController.php
class ApplicantPrivacyController extends Controller
{
/**
* Aday: "Benim başvurumla ilgili verileri görüntülemek istiyorum"
*/
public function submitAccessRequest(Request $request)
{
$request->validate([
'email' => 'required|email',
'position' => 'nullable|string',
]);
// Veribenim üzerinden DSAR kaydı aç
$dsar = Veribenim::submitDsar(
requestType: 'access',
fullName: auth()->user()?->name ?? 'Başvuru Sahibi',
email: $request->email,
description: sprintf(
'Veribenim e-posta: %s adresinde %s pozisyonuna yaptığım başvuru ' .
'hakkında tüm kişisel verilerime erişmek istiyorum. ' .
'KVKK Madde 11/a kapsamında.',
$request->email,
$request->position ?? '(belirtilmemiş)'
),
);
return response()->json([
'message' => 'Başvurunuz alındı. 30 gün içinde e-postanıza yanıt göndereceğiz.',
'dsar_id' => $dsar['id'],
'deadline' => now()->addDays(30)->format('d.m.Y'),
]);
}
/**
* Aday: "Verilerimi sil"
*/
public function submitErasureRequest(Request $request)
{
$request->validate([
'email' => 'required|email',
'reason' => 'nullable|in:no_longer_interested,data_deletion,other',
]);
Veribenim::submitDsar(
requestType: 'erasure',
fullName: auth()->user()?->name ?? 'Başvuru Sahibi',
email: $request->email,
description: 'Başvurumla ilgili tüm kişisel verilerimin silinmesini talep ediyorum. ' .
'KVKK Madde 11/e kapsamında "Veri Silme Hakkı"nı kullanıyorum.'
);
return response()->json([
'message' => 'Veri silme talebiniz alındı.',
'deadline' => now()->addDays(30)->format('d.m.Y'),
]);
}
}
11. Sonuç: Veribenim ile KVKK Uyumlu İşe Alım
Bu makale gösteriyor ki:
CV Saklama Süresi: İşe alınamış = 2 yıl, İşe alınmış = 10 yıl (İş İlişkisi sonrası)
Özel Nitelikli Veriler: Fotoğraf, sağlık, sosyal medya = Ayrı consent gerekli
Sosyal Medya Araştırması: 30 gün sonra otomatik silin, stalk riski yoktur
Data Sovereignty: USA-tabanlı ATS = KVKK ihlali
Automated Privacy: Veribenim ile tüm anonimleştirme takvimlenmiş
DSAR: Aday isterse veri erişim/silme 30 gün içinde yerine getirilir
Veribenim Compliance as a Service platformu ile, HR yöneticileri kod yazmadan, sadece sistem yapılandırması ile KVKK uyumlu kalmayı otomatikleştirebilir.
📚 Teknik Dokümantasyon: Tüm SDK referans dökümanlarına veribenim.com/documents adresinden ulaşabilirsiniz.
Sonraki Adım
Veribenim'in bu sektör için sunduğu çözümleri görmek ve KVKK/GDPR uyumlu pazarlama başlamak için aşağıdaki butona tıklayın.
Ücretsiz Hesap Oluştur