Webhook endpointlerinizi HMAC-SHA256 imzalama, zaman damgası doğrulama ve tekrar saldırısı önleme ile güvence altına alma rehberi.
Webhook'lar Acik Kapilardir — Güvenligini Saglayin
İçerik otomasyon API'sini (n8n is akislari, özel entegrasyonlar, harici CMS kopruler) kullanan her JekCMS sitesi en az bir webhook endpoint'i ortaya cikarir. Bu endpoint internetten POST istekleri kabul eder, veriyukunu isler ve veritabanıniza veri yazar. Istegin gerçekten yetkili gondereninizdenn geldigini doğrulamazsaniz, URL'yi kesfeden herkes sitenize içerik enjekte edebilir.
Bunu Ocak 2026'da zor yoldan ogrendim. Musteri sitelerimizden biri sabah saat 3'te spam yazilar yayinlamaya basladi. n8n webhook URL'si yanlis yapılandırilmis bir kayit servisi araciligiyla sizmisti ve birisi oluşturulmus yuk verilerini doğrudan endpoint'e gonderiyordu. Imza doğrulamasi yoktu — endpoint iyi bicimlendirilmis herhangi bir JSON govdesini kabul ediyordu. 47 spam yaziyi temizlemek ve ilk gunden insa etmem gereken imzalama mekanizmasini uygulamak 20 dakika surdu.
Bu rehber, artik tum JekCMS sitelerinde kullandigimiz eksiksiz webhook güvenlik uygulamasini anlatmaktadir. Buradaki her teknik production'da çalışır ve site agimiz genelinde gunde yaklasik 2.000 webhook cagrisini isler.
HMAC-SHA256 Imzalama Nasil Calisir
HMAC (Hash-based Message Authentication Code), paylasilan bir gizli anahtar kullanarak her istek için benzersiz bir imza oluşturur. Gonderici gizli anahtari kullanarak istek govdesinin bir hash'ini hesaplar, bunu baslik olarak ekler ve alici ayni hash'i yeniden hesaplayarak istegin degistirilmedigini ve gizli anahtari bilen birinden geldigini doğrular.
Algoritma basit bir dille:
- Gonderici veri yukunu JSON'a seriler
- Gonderici
HMAC-SHA256(json_govde, gizli_anahtar)hesaplar - Gonderici istegi baslikta hash ile gonderir:
X-Webhook-Signature: sha256=abc123... - Alici ham istek govdesini okur
- Alici kendi gizli anahtar kopyasini kullanarak ayni HMAC'i hesaplar
- Alici iki hash'i karşılaştırir — eslesirlerse istek gerçektir
Gizli anahtar asla ag üzerinden seyahat etmez. Birisi istegi yakalasin bile, anahtari bilmeden gecerli bir imza oluşturamaz.
Gonderici Tarafi: PHP'de Istekleri Imzalama
JekCMS'in giden webhook isteklerini nasıl imzaladigi (JekCMS'in kendisi harici servislere webhook tetiklediginde kullanilir):
class WebhookSender {
private string $secret;
private string $endpointUrl;
public function send(array $payload): array {
$jsonBody = json_encode($payload, JSON_UNESCAPED_UNICODE);
$timestamp = time();
$signature = $this->computeSignature($jsonBody, $timestamp);
$headers = [
'Content-Type: application/json; charset=utf-8',
'X-Webhook-Signature: sha256=' . $signature,
'X-Webhook-Timestamp: ' . $timestamp,
'X-Webhook-Id: ' . bin2hex(random_bytes(16)),
];
$ch = curl_init($this->endpointUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $jsonBody,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'success' => $httpCode >= 200 && $httpCode < 300,
'http_code' => $httpCode,
];
}
private function computeSignature(string $body, int $timestamp): string {
$signedContent = $timestamp . '.' . $body;
return hash_hmac('sha256', $signedContent, $this->secret);
}
}
Dikkat edilmesi gereken iki tasarim karari:
- Imzada zaman damgasi: Hash'lemeden once zaman damgasini govde ile birlestiriyoruz. Bu, ayni veriyukununun farkli zamanlarda gonderilmesinin iki farkli imza uretecegi anlamina gelir ve tekrar saldirisi önleme için kritiktir.
- Webhook ID: Her istek
random_bytes(16)araciligiyla benzersiz bir kimlik alir. Bu, alicinin yeniden deneme mantigi ayni yuku iki kez gonderdiginde yinelenen teslimatlari tespit etmesini ve reddetmesini sağlar.
Alici Tarafi: Gelen Webhook'lari Dogrulama
Bu daha onemli yarimdir. JekCMS'in API işleyicisinden tam doğrulama middleware'i:
class WebhookVerifier {
private string $secret;
private int $maxAge;
public function verify(): bool {
$rawBody = file_get_contents('php://input');
if (empty($rawBody)) {
$this->reject('Bos istek govdesi', 400);
return false;
}
$signatureHeader = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
if (strpos($signatureHeader, 'sha256=') !== 0) {
$this->reject('Eksik veya hatali imza basligi', 401);
return false;
}
$receivedSignature = substr($signatureHeader, 7);
$timestamp = (int)($_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? 0);
$age = abs(time() - $timestamp);
if ($age > $this->maxAge) {
$this->reject("Istek cok eski: {$age}s", 401);
return false;
}
$signedContent = $timestamp . '.' . $rawBody;
$expectedSignature = hash_hmac('sha256', $signedContent, $this->secret);
if (!hash_equals($expectedSignature, $receivedSignature)) {
$this->reject('Imza uyumsuzlugu', 401);
return false;
}
return true;
}
}
hash_equals Neden Onemli
hash_equals() fonksiyonu karşılaştırma için === yerine kullanilir. Bu bir stil tercihi degil — bir güvenlik gereksinimidir. Standart dize karşılaştırmasi (===), ilk uyumsuz karakterde erken cikar, yani daha fazla karakter eslesen imzalari reddetmek biraz daha uzun surer. Bir saldirgan bu zamanlama farklarini olcerek, her seferinde bir karakter gecerli bir imzayi yeniden oluşturabilir. Buna zamanlama saldirisi denir.
hash_equals() kac karakter esledigine bakmaksizin her zaman ayni miktarda zaman alir ve bu yan kanali tamamen ortadan kaldirir.
Tekrar Saldirisi Onleme
Gecerli bir HMAC imzasiyla bile, bir saldirgan tarafindan yakalanan mesru bir istegin tekrari tehlikeli olabilir. Zaman damgasi mekanizmasi bunun cogunu halleder — 5 dakikadan (300 saniye) eski herhangi bir istegi reddederiz. Ek koruma icin, ayni istegin iki kez islenmesini önlemek için webhook kimliklerini takip ederiz.
Webhook kimlik takibi için dosya tabanli depolama kullaniyoruz cunku sifir ek altyapi gerektiriyoruz. Her webhook kimlik küçük bir dosya (sadece bir zaman damgasi) oluşturur. Tekrar penceresnden eski dosyalar olasiliksal olarak temizlenir. Gunde 10.000'den fazla webhook isleyen siteler için Redis veya veritabanı tablosu daha verimli olur, ancak dosya tabanli depolama mevcut hacmimizi sorunsuz halleder.
IP Beyaz Listesi
Imza doğrulamasi istegin gerçek oldugunu soyler. IP beyaz listesi istegin beklenen bir agdan geldigini soyler. Derinlemesine savunma için her iki katibi birlikte kullaniriz.
n8n webhook entegrasyonlari için IP araligi bilinen ve kararliydir. Harici servisler için cogu dokumantasyonlarinda webhook IP aralik larini yayinlar.
Bir uyari: sunucunuz bir ters proxy veya CDN'in (ornegin Cloudflare) arkasindaysa, $_SERVER['REMOTE_ADDR'] orijinal istemci IP'si degil proxy IP'si olacaktir. Bu durumda X-Forwarded-For veya CF-Connecting-IP basligini okumaniz gerekir. Ancak dikkatli olun — istek proxy'yi atlayinca bu basliklar taklit edilebilir.
Veri Yuku Dogrulama
Imzali, zaman damgali, IP doğrulanmis bir istek hala kotu niyetli veri icerebilir. Veri yukun kendisi veritabanıniza dokunmadan once doğrulamaya ihtiyac duyar.
Beyaz liste yaklasimi esastir. Tehlikeli alanlari engellemeye çalışmak yerine, tam olarak hangi alanlarin izin verildigini tanimliyoruz ve geri kalan her seyi reddediyoruz. Bu, bir saldirganaun saf bir toplu atama modeli tarafindan islenebilecek role veya is_admin gibi alanlar gondermesini onler.
Her Seyi Bir Araya Getirmek
Dort savunma katmani, her biri farkli bir saldiri sinifini yakalar. Sira onemlidir: IP kontrolu en ucuzdur (kripto yok), bu yuzden once çalışır. HMAC doğrulamasi siradakidir (bir hash hesaplamasi). Tekrar kontrolu ucuncudur (bir dosya arama). Veri yuku doğrulama son siradadir (en cok CPU yogun). Saldiri 1. katmanda basarisiz olursa, 2-4 katmanlari için asla kaynak harcamiyoruz.
Hata Yönetimi ve Kayit Tutma
Bir webhook basarisiz oldugunda, nedenini bilmeniz gerekir — ancak neleri kaydettiginiz konusunda dikkatli olmalisiniz. Gizli anahtari, tam imzayi veya eksiksiz istek govdesini (hassas içerik iceriyor olabilir) asla kaydetmeyin. Kayit dosyalarinizda güvenlik riski oluşturmadan sorunu teshis etmeye yetecek kadar kaydedin.
Kayit yaklasimimiz sunu kaydeder: zaman damgasi, IP adresi, kisaltilmis User-Agent, red nedeni ve varsa webhook kimlik. Kayitlari gunluk olarak donduruyoruz ve 30 gunluk gecmis tutuyoruz.
Anahtar Rotasyonu
Gizli anahtarlar duzensiz olarak dondukulmelidir. Webhook gizli anahtarlarini her 90 gunde dondururuz. Dondurucu işlemi, hem eski hem de yeni gizli anahtarin kabul edildigi kisa bir cakisma donemine destek verir. Bu, alici tarafta dondurmenizi tamamlamadan gonderici tarafini güncellemenize olanak tanir.
Yeni gizli anahtari once aliciya dagitayim (hem eski hem yeniyi kabul eden), sonra gondericiyi güncelle, sonra her seyin calistigini doğruladiktan sonra eski gizli anahtari alicidan kaldir.
Webhook güvenligi, birisi endpoint'inizi buluncaya kadar asiri gibi hissedilen seylerden biridir. Yukaridaki uygulama istek başına yaklasik 2ms ek yuk ekler. Sitenize bir API araciligiyla giren her içerik parcasinin yetkilendirildigini bilmek için bu küçük bir bedeldir.