B2B SaaS Platformlarında KVKK Kabusu ve Veribenim Çözümü: Multi-Tenant Veri Ayrıştırma ve API ile İzole Çerez Yönetimi
Birçok SaaS kurucusu, hukuki sorumluluktan kurtulmak için söyle demiştir: > "Biz platform sağlayıcısı, müşterilerin ne yaptığından sorumlu değiliz." **KVKK Madde 2 ve GDPR Article 28 bu yanlış yorumu çöper:** ``` Veri İşleyen, Veri Sorumlusu'nun talimatı üzerine kişiye ilişkin verileri işler. Hukuki sorumluluğu Veri Sorumlusu üstlenir. ``` Yani: - **Veri Sorumlusu (DPA'da)**: SaaS müşterisi (B2B client) - **Veri İşleyen**: SaaS firması - **Sorumlu**: **İkisi de** — ancak farklı seviyelerde SaaS firması, eğer: - Konum verisi saklar - Kullanıcı davranış analizi yapar - Çerez yönetimi sunar - Müşteri İP adresini loglar ...bunları **kontrol altında** tutmazsa, **ortakça sorumlu** sayılabilir.

"Ben Sadece Altyapı Sağlıyorum" Yanılgısı
Birçok SaaS kurucusu, hukuki sorumluluktan kurtulmak için söyle demiştir:
"Biz platform sağlayıcısı, müşterilerin ne yaptığından sorumlu değiliz."
KVKK Madde 2 ve GDPR Article 28 bu yanlış yorumu çöper:
Veri İşleyen, Veri Sorumlusu'nun talimatı üzerine kişiye ilişkin
verileri işler. Hukuki sorumluluğu Veri Sorumlusu üstlenir.
Yani:
- Veri Sorumlusu (DPA'da): SaaS müşterisi (B2B client)
- Veri İşleyen: SaaS firması
- Sorumlu: İkisi de — ancak farklı seviyelerde
SaaS firması, eğer:
- Konum verisi saklar
- Kullanıcı davranış analizi yapar
- Çerez yönetimi sunar
- Müşteri İP adresini loglar
...bunları kontrol altında tutmazsa, ortakça sorumlu sayılabilir.
🚨 İHLAL RİSKİ: Pek çok SaaS, multi-tenant mimaride veri sızıntısına izin verir. Örneğin, Tenant A'nın API anahtarı kullanılarak Tenant B'nin kullanıcı verisi çekilebilir. KVKK Madde 3 ihlali = 500,000 TL - 10 milyon TL ceza.
Bu makalede, Veribenim'in tenant-level API'si kullanarak her müşterinin gizlilik ayarlarını izole etmek, göstereceğiz.
1. Veri İşleyen vs. Veri Sorumlusu Ayrımı
KVKK'nın mutsuzlaştırıcı yanı, SaaS için iki farklı sorumluluk tanımlamasıdır:
Veri Sorumlusu (Data Controller)
"Kişiye ilişkin verilerin işlenmesi amacını ve araçlarını belirleyen gerçek
veya tüzel kişi"
Örnek: Pazarlama SaaS'ının B2B müşterisi (email kampanyası yapan şirket).
Sorumlulukları:
- ✅ Hangi veriyi toplayacağım? Kararı
- ✅ Kaç gün tutacağım? Kararı
- ✅ Kimle paylaşacağım? Kararı
- ⚖️ Cezadan sorumlu: Madde 12-19 ihlalleri
Veri İşleyen (Data Processor)
"Veri Sorumlusu'nun talimatı üzerine kişiye ilişkin verileri işlemeyi
meslek ve faaliyeti gereği yapan gerçek veya tüzel kişi"
Örnek: Pazarlama SaaS'ının kendisi (platform sağlayıcısı).
Sorumlulukları:
- ✅ Verileri güvenli sakla (TDE, backup, access control)
- ✅ Müşterinin talimatını uygula (data deletion vb.)
- ✅ Alt-işleyenleri denetleyin (CDN, email service vb.)
- ⚖️ Cezadan sorumlu: Madde 3, 12/1 ihlalleri (5 milyon - 10 milyon TL)
⚖️ HUKUKİ NOT: GDPR Article 28'de aynı mantık var. AB ve Türkiye arasında veri transferi varsa, her iki taraf da KVKK + GDPR'ye uymalıdır.
Kritik SaaS Senaryoları
| Senaryo | Veri Sorumlusu | Veri İşleyen | Sorun |
|---|---|---|---|
| Email gönderme platformu | Müşteri (pazarlama şirketi) | SaaS firması | SaaS veri koruması sorumlu |
| CRM | Müşteri (satış şirketi) | SaaS + Email servisi | Her ikisi de sorumlu |
| Analytics platform | Müşteri (website sahibi) | SaaS + Google Cloud | SaaS veri transfer kontrolü sorumlu |
| Kurumsal PDF editor | Müşteri (şirket) | SaaS + S3 storage | SaaS depolama güvenliği sorumlu |
2. Multi-Tenant Mimaride Veri Sızıntısı Riski
Çoğu B2B SaaS, aynı database'de farklı müştereri (tenant) verilerini barındırır. Eğer tenant-isolation doğru değilse, felakete davetiye çıkarır:
Sızıntı Senaryosu: Şoşyal Medya Analiz SaaS
Tenant A (Sosyal Medya Ajansı A):
- Üst müşterileri: Coca-Cola, Pepsi
- Takip ettiği kullanıcı kimlikleri (IP, email, device ID) database'de
Tenant B (Sosyal Medya Ajansı B):
- Üst müşterileri: Fanta, Sprite
- Farklı kullanıcı verisi
Sorun: Tenant A'nın API key'i ile Tenant B'nin /users endpoint'ine eriş?
KVKK Madde 3 İhlali:
- Veri Işleyen (SaaS), verileri "güvenli ortamda" saklamamıştır
- Sorumlu kişi izni olmaksızın diğer müşterilerin verilerine eriş sağlanmıştır
- Ceza: 500,000 TL - 10 milyon TL
3. Nasıl Entegre Edilir? — @veribenim/core Tenant-Level API
Veribenim, her tenant'ın kendi token'ı ile izole tercihler saklamasını sağlar.
Adım 1: Kurulum
npm install @veribenim/core @veribenim/react @veribenim/nextjs
Adım 2: Tenant-Isolated Consent Manager
// lib/tenantConsentManager.ts
import { init } from '@veribenim/core';
export class TenantConsentManager {
private clients = new Map<string, ReturnType<typeof init>>();
/**
* Her tenant'ın kendi Veribenim instance'ı
*/
getClient(tenantToken: string) {
if (!this.clients.has(tenantToken)) {
this.clients.set(tenantToken, init({
token: tenantToken,
lang: 'tr',
}));
}
return this.clients.get(tenantToken)!;
}
/**
* Tenant A'nın kullanıcısı için tercihler al
*/
async getTenantConsent(tenantToken: string, endUserId: string) {
const client = this.getClient(tenantToken);
return await client.getPreferences(endUserId);
}
/**
* Tenant B'nin tercihlerinde etki yapılamaz
* (farklı token, farklı isolation)
*/
async saveTenantConsent(
tenantToken: string,
endUserId: string,
preferences: Record<string, boolean>
) {
const client = this.getClient(tenantToken);
return await client.savePreferences(endUserId, preferences);
}
/**
* DSAR (Silme Talebi) — Tenant-scoped
*/
async submitTenantDsar(
tenantToken: string,
payload: {
requestType: 'access' | 'erasure' | 'portability';
fullName: string;
email: string;
description: string;
}
) {
const client = this.getClient(tenantToken);
return await client.submitDsar(payload);
}
}
export const tenantManager = new TenantConsentManager();
Adım 3: Tenant Config ve Setup
// lib/tenant.ts
import { prisma } from '@/lib/prisma';
export async function getTenantConfig(tenantId: string) {
const tenant = await prisma.tenant.findUnique({
where: { id: tenantId },
select: {
id: true,
name: true,
veribenimToken: true, // ← Kritik: Her tenant'ın kendi token'ı
locale: true,
privacyPolicy: true,
consentCookies: true,
},
});
if (!tenant) {
throw new Error(`Tenant ${tenantId} bulunamadı`);
}
return tenant;
}
// Veritabanı Schema (örnek)
/*
CREATE TABLE tenants (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL,
veribenimToken VARCHAR(255) NOT NULL UNIQUE, -- Her tenant UNIQUE token
locale VARCHAR(10) DEFAULT 'tr',
privacyPolicy TEXT,
consentCookies JSON,
createdAt TIMESTAMP,
updatedAt TIMESTAMP
);
*/
4. Nasıl Entegre Edilir? — Next.js SaaS Dashboard
Multi-tenant Next.js uygulamasında, dinamik routing + Veribenim tercihlerini birleştiriyoruz.
App Router Layout — Tenant-Scoped
// app/[tenantSlug]/layout.tsx
import { VeribenimProvider } from '@veribenim/nextjs';
import { getTenantConfig } from '@/lib/tenant';
import { notFound } from 'next/navigation';
export default async function TenantLayout({
children,
params,
}: {
children: React.ReactNode;
params: { tenantSlug: string };
}) {
let tenant;
try {
tenant = await getTenantConfig(params.tenantSlug);
} catch (error) {
notFound();
}
return (
<VeribenimProvider
config={{
token: tenant.veribenimToken,
lang: tenant.locale,
}}
>
<div className="tenant-layout">
<header>
<h1>{tenant.name} - Gizlilik Kontrol Paneli</h1>
</header>
{children}
</div>
</VeribenimProvider>
);
}
Consent Dashboard Bileşeni
// app/[tenantSlug]/consent/page.tsx
'use client';
import { useVeribenim } from '@veribenim/react';
import { useParams } from 'next/navigation';
import { useState } from 'react';
export default function ConsentDashboard() {
const params = useParams();
const tenantSlug = params.tenantSlug as string;
const { preferences, savePreferences, loading } = useVeribenim();
const [saved, setSaved] = useState(false);
if (loading) return <div>Tercihler yükleniyor...</div>;
const handleToggle = async (category: string, value: boolean) => {
const updated = {
...preferences,
preferences: {
...(preferences?.preferences || {}),
[category]: value,
},
};
await savePreferences(updated);
setSaved(true);
setTimeout(() => setSaved(false), 2000);
};
return (
<div className="consent-dashboard">
<h2>Veri Gizlilik Tercihlerim</h2>
<div className="consent-group">
<h3>Gerekli Çerezler</h3>
<p>Platform işletmek için zorunludur.</p>
<label disabled>
<input type="checkbox" checked disabled readOnly />
Session & Güvenlik Çerezleri
</label>
</div>
<div className="consent-group">
<h3>Analitik & Performans</h3>
<label>
<input
type="checkbox"
checked={preferences?.preferences?.analytics ?? false}
onChange={(e) => handleToggle('analytics', e.target.checked)}
/>
Platform kullanımımı analiz etmeye izin ver
<small>
Sayfa yükleme hızı, tıklama tepkisi vb. ölçümleri iyileştirmek için
</small>
</label>
</div>
<div className="consent-group">
<h3>Pazarlama</h3>
<label>
<input
type="checkbox"
checked={preferences?.preferences?.marketing ?? false}
onChange={(e) => handleToggle('marketing', e.target.checked)}
/>
Yeni özellikleri ve ilgili ürünleri öğren
<small>
Email bildirimleri alabilir, re-targeting reklamları görebilirsiniz
</small>
</label>
</div>
{saved && <p className="success-message">✓ Tercihler kaydedildi</p>}
<button onClick={() => handleDataExport()}>
Verilerim İndir (DSAR)
</button>
<button onClick={() => handleDataErasure()} className="danger">
Verilerim Silinsin
</button>
</div>
);
}
async function handleDataExport() {
const response = await fetch('/api/[tenantSlug]/dsar/access', {
method: 'POST',
});
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'verilerim.json';
a.click();
}
async function handleDataErasure() {
if (!confirm('Tüm verileriniz silinecek. Emin misiniz?')) return;
await fetch('/api/[tenantSlug]/dsar/erasure', { method: 'POST' });
alert('Silme isteği alındı, 30 gün içinde işlenir.');
}
5. Nasıl Entegre Edilir? — Laravel Multi-Tenant Backend
Laraven 11+ ile multi-tenant SaaS arka ucu kuruyoruz.
Adım 1: Package Kurulumu
composer require veribenim/laravel-sdk
composer require stancl/tenancy
php artisan tenancy:install
Adım 2: Tenant Veritabanı Modeli
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tenant extends Model
{
protected $fillable = [
'name',
'slug',
'domain',
'veribenim_token', // ← Kritik: Her tenant'ın kendi token'ı
'veribenim_region', // Türkiye / EU
'locale',
'subscription_plan',
];
public function users()
{
return $this->hasMany(User::class);
}
}
Adım 3: Tenant-Scoped DSAR Controller
<?php
namespace App\Http\Controllers;
use Veribenim\Laravel\VeribenimFacade as Veribenim;
use App\Models\Tenant;
use Illuminate\Http\Request;
class TenantDsarController extends Controller
{
/**
* Tenant A'nın DSAR isteği (Tenant B'den izole)
*/
public function submitRequest(Request $request, string $tenantSlug)
{
$tenant = Tenant::where('slug', $tenantSlug)
->firstOrFail();
// 1. Tenant'ın kendi Veribenim client'ını oluştur
$veribenimClient = new \Veribenim\VeribenimClient(
token: $tenant->veribenim_token,
region: $tenant->veribenim_region ?? 'tr',
);
// 2. DSAR isteğini gönder
$response = $veribenimClient->submitDsar(
requestType: $request->enum('type', ['access', 'erasure', 'portability']),
fullName: $request->string('full_name'),
email: $request->email('email'),
description: sprintf(
'[Tenant: %s] %s',
$tenant->name,
$request->string('description')
)
);
// 3. Log kaydet
\Log::info('[DSAR] Tenant isteği gönderildi', [
'tenant_id' => $tenant->id,
'tenant_name' => $tenant->name,
'request_type' => $request->type,
'veribenim_request_id' => $response['id'] ?? null,
]);
return response()->json([
'status' => 'received',
'deadline' => now()->addDays(30),
'request_id' => $response['id'] ?? null,
]);
}
/**
* Tenant B'nin DSAR isteği — Tenant A'dan TAMAMEN İZOLE
* (Aynı database'de, farklı token, farklı veri)
*/
public function submitRequestForTenantB(Request $request)
{
// Tenant B'nin token'ı kullanılır, Tenant A'nın değil
// Veritabanında farklı satırlara eriş
// Hiçbir veri karışması olmaz
}
/**
* Tenant'ın DSAR isteğinin durumunu kontrol et
*/
public function checkStatus(string $tenantSlug, string $requestId)
{
$tenant = Tenant::where('slug', $tenantSlug)->firstOrFail();
$veribenimClient = new \Veribenim\VeribenimClient(
token: $tenant->veribenim_token
);
$status = $veribenimClient->getDsarStatus($requestId);
return response()->json($status);
}
}
Adım 4: Middleware — Tenant Validation
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ValidateTenantAccess
{
public function handle(Request $request, Closure $next)
{
$tenantSlug = $request->route('tenantSlug');
$tenant = \App\Models\Tenant::where('slug', $tenantSlug)
->firstOrFail();
// Authenticated kullanıcı bu tenant'a ait mi?
if ($request->user()?->tenant_id !== $tenant->id) {
return response()->json(['error' => 'Unauthorized'], 403);
}
// Tenant context'i request'e ata
$request->merge(['tenant' => $tenant]);
return $next($request);
}
}
6. Data Sovereignty: Her Tenant'ın Verisi Kendi Alanında
GDPR Article 44 ve KVKK Madde 9, kişiye ilişkin verilerin sınır ötesi transferini kısıtlar.
Sorun: Multi-Region SaaS
Tenant A (TR müşteri):
- Verileri AWS Frankfurt'a gönder (AB veri savunması: ✅ İzin)
Tenant B (JP müşteri):
- Verileri AWS Tokyo'ya gönder (Yetersiz veri koruması: ❌ Risk)
Tenant C (US müşteri):
- Verileri AWS Virginia'ya gönder (Privacy Shield yok: ❌ Riskli)
Çözüm: Veribenim Regional Tokens
Veribenim, her tenant'ın bölge tercihini respekt eder:
// Tenant config
$tenant = Tenant::find(1);
$tenant->update([
'veribenim_region' => 'tr', // Verileri Türkiye'de tut
]);
// SDK otomatik routing
$veribenim = new Veribenim\VeribenimClient(
token: $tenant->veribenim_token,
region: $tenant->veribenim_region // ← Lokalisasyon
);
// Aynı tenant için EU veri?
$euTenant = Tenant::find(2);
$euTenant->update([
'veribenim_region' => 'eu', // EU GDPR veri merkezi
]);
7. Compliance as a Service: SaaS Firmaları İçin Hazır Uyumluluk
Veribenim, SaaS'ların "hazır uyumluluk" paketlerini sunar:
Package 1: Tenant Isolation
✅ Her tenant kendi Veribenim token'ı
✅ Database-level isolation (Row-Level Security)
✅ API key rotation (otomatik)
✅ Audit logging (her işlem loglanır)
Package 2: DSAR Pipeline
✅ Veri İndir (access request)
✅ Veri Sil (erasure request)
✅ Veri Taşı (portability request)
✅ Otomatik 30 günlük deadline tracking
✅ KVKK + GDPR uyumlu raporlama
Package 3: Consent Management
✅ Tenant-level tercih saklama
✅ End-user tercih yönetimi
✅ Cookie banner integration
✅ Otomatik consent renewal (yıllık)
8. Pratik Örnek: B2B Email Marketing SaaS
Senaryo: EmailBlast adlı bir email pazarlama platformu.
Tenant A: Coca-Cola'nın Marketing Agency'si
Veribenim Token: pk_live_tenant_a_xyzabc
Bölge: TR (Türkiye veri merkezi)
Müşteri Listesi:
- 50 milyon Türkiye telefon numarası
- SMS + Email konsentleri
EmailBlast, Coca-Cola'nın verilerini:
- 30 gün saklar (kampanya bitince siler)
- Başkasına satmaz (izolasyon)
- Analytics'e vermesfr (tenant-scoped)
Tenant B: Pepsi'nin Marketing Agency'si
Veribenim Token: pk_live_tenant_b_def456
Bölge: EU (GDPR veri merkezi)
Müşteri Listesi:
- 80 milyon EU email adresi
- GDPR uyumlu onaylar
EmailBlast, Pepsi'nin verilerini:
- 60 gün saklar (sözleşmeye göre)
- Coca-Cola'dan izole (Tenant A'nın API key'i Tenant B'ye erişemez)
- EU veri merkezinde (GDPR compliant)
Sorun: API Sızıntısı
Eğer EmailBlast'ın API architecture yanlışsa:
GET /api/subscribers?api_key=tenant_a_key
→ Tenant B'nin verilerini dönebilir (hata!)
Çözüm: Veribenim tenant-isolation
GET /api/[tenantSlug]/subscribers
→ Middleware otomatik olarak tenant kontrol eder
→ Tenant A'nın API key'i Tenant B'ye erişemez
→ KVKK Madde 3 ihlali önlenir
9. Sık Yapılan Hatalar
Hata 1: Tüm Tenantlar İçin Aynı Veribenim Token
// ❌ YANLIŞ
const VERIBENIM_TOKEN = 'pk_live_shared_token';
class TenantConsentManager {
getClient(tenantSlug) {
// Tenant A ve B aynı token'ı kullanır!
return init({ token: VERIBENIM_TOKEN });
}
}
Sorun: Multi-tenant ayrımı yok, tüm veriler karışabilir.
// ✅ DOĞRU
class TenantConsentManager {
getClient(tenantSlug) {
const tenant = Tenant.find(tenantSlug);
// Her tenant'ın kendi token'ı
return init({ token: tenant.veribenim_token });
}
}
Hata 2: Müşteri Tercihlerini Sadece Frontend'de Tutma
// ❌ YANLIŞ
const [preferences, setPreferences] = useState({
marketing: true,
analytics: false,
});
// ... tercihler localStorage'e kaydedilir
// ... Sunucuya sorgulanmaz
// ... Veri Sorumlusu (B2B müşteri) tercihlerden habersiz
Sorun: Backend validasyonu yok, B2B müşteri politikasını uygulayamaz.
// ✅ DOĞRU
const { preferences, savePreferences } = useVeribenim();
// Veribenim API'si tercihler kaydeder
// Backend tercih validasyonu yapar
// B2B müşteri tercihler raporunu alabilir
Hata 3: DSAR İsteğini Ignore Etme
// ❌ YANLIŞ
public function submitDsar(Request $request) {
// İstek alındı, ama işlem yapılmadı
return response()->json(['status' => 'ok']);
}
Sorun: KVKK Madde 11 ihlali, 50,000 TL - 500,000 TL ceza.
// ✅ DOĞRU
public function submitDsar(Request $request) {
Veribenim::submitDsar(
requestType: $request->type,
// ... detaylar
);
// Lokal silme işlemleri
if ($request->type === 'erasure') {
User::where('id', $request->user_id)->delete();
UserActivity::where('user_id', $request->user_id)->delete();
}
return response()->json([
'status' => 'received',
'deadline' => now()->addDays(30),
]);
}
10. Checklist: KVKK Uyumlu Multi-Tenant SaaS
- Her tenant kendi Veribenim token'ına sahip
- Tenant isolation middleware uygulanmış
- API key routing tenant-scoped
- DSAR pipeline entegre edilmiş
- Veri retention politikası tanımlanmış
- Veri İşlem Sözleşmesi (DPA) hazır
- B2B müşterilere uyumluluk raporu sağlanıyor
- End-user tercih yönetimi sunuluyor
- Audit logging aktif (her işlem kaydedilir)
- Yıllık uyumluluk denetimi yapılıyor
11. Veribenim Multi-Tenant Avantajları
| Özellik | Sağlanan Avantaj |
|---|---|
| Token per Tenant | Tam izolasyon, data sızıntısı riski sıfır |
| Automatic Isolation | Middleware-level protection, developer hatasını önler |
| DSAR Pipeline | 30 günde compliance, otomatik raporlama |
| Regional Storage | GDPR + KVKK uyumlu veri lokasyonu |
| Consent Management | B2B müşteri politikasını end-user tercihine uygulanır |
| Audit Logs | Denetçiye hazır, tamamen görünürlük |
| Zero-Knowledge | Veribenim mühendisleri verilerinizi asla görmez |
Kaynaklar ve Linkler
- KVKK Madde 2: Kişiye ilişkin veri işlemesinin tanımı
- KVKK Madde 3: Veri güvenliği
- KVKK Madde 11: Veri Sahibinin Hakları (DSAR)
- GDPR Article 28: Data Processor responsibilities
- GDPR Article 44: Data transfers rules
- Kişisel Verileri Koruma Kurulu Serbest Bölgeye İlişkin Kararı: Multi-region guidance
📚 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