PHP ile Güvenli Webhook Doğrulama: GitHub Örneği
Bir webhook adresi oluşturmak kolay; gelen isteğin gerçekten beklediğin kaynaktan geldiğini doğrulamak ise ayrı bir konu. GitHub ve PHP üzerinden imza doğrulamasını inceleyelim.
Bir uygulamada sipariş oluştuğunda, ödeme durumu değiştiğinde veya bir depoya yeni kod gönderildiğinde başka bir sistemin bundan haberdar olması gerekebilir. Her saniye karşı tarafa “yeni bir şey var mı?” diye sormak yerine, olay gerçekleştiğinde bildirim almak için webhook kullanabiliriz.
Ancak internete açık bir adrese gelen her POST isteğini güvenilir kabul etmek doğru değildir. Birisi aynı adrese kendi isteğini gönderebilir. Bu yazıda GitHub’ın webhook imzasını PHP ile doğrulayan küçük bir örnek hazırlayacağız; ardından gerçek kullanımda nelere dikkat edilmesi gerektiğini konuşacağız.
Önemli sınır: Örnek GitHub’ın imza biçimine özeldir. Başka bir ödeme, kargo veya e-ticaret servisinin başlıklarını ve imzalama kurallarını aynı varsayma; sağlayıcının kendi dokümantasyonunu takip et.
Webhook ile normal API çağrısının farkı ne?
Normal bir API çağrısında uygulaman genellikle karşı tarafa isteği başlatan taraftır. Webhook’ta ise olay gerçekleştiğinde servis, senin belirlediğin adrese HTTP isteği gönderir.
Örneğin GitHub’da bir olay olduğunda isteğin gövdesinde olay verileri; başlıklarında ise olay türü, teslimat kimliği ve yapılandırılmışsa imza bulunur. Uygulaman bunları doğrulayıp uygun işlemi başlatır.
Buradaki kritik nokta şu: JSON içeriğinin düzgün görünmesi, isteğin güvenilir olduğunu göstermez. Önce kimden geldiğine ve içeriğin değişip değişmediğine dair kontrol gerekir.
GitHub imzası nasıl çalışır?
GitHub webhook ayarlarında bir secret belirlersin. GitHub, istek gövdesi ve bu gizli anahtarla HMAC-SHA256 imzası üretir; sonucu X-Hub-Signature-256 başlığında gönderir. Sen de aynı ham gövde üzerinde hesaplama yapıp değerleri karşılaştırırsın.
Burada “ham gövde” önemli. JSON’u önce çözüp sonra yeniden oluşturarak imza üretirsen boşluklar veya karakter gösterimi değişebilir; anlamı aynı olsa bile imza eşleşmeyebilir. GitHub’ın resmî imza doğrulama rehberi bu biçimi ve test değerlerini açıklıyor.
PHP ile küçük bir doğrulama örneği
Aşağıdaki dosya, yalnızca GitHub’ın ping olayını karşılamak için hazırlanmış bir başlangıç örneğidir. Sipariş oluşturmaz, kuyruğa iş eklemez ve başka olayları işlemez:
<?php
declare(strict_types=1);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
header('Allow: POST');
exit;
}
$secret = getenv('GITHUB_WEBHOOK_SECRET');
if (!is_string($secret) || $secret === '') {
http_response_code(500);
exit;
}
// Bu örnek 256 KB ile sınırlıdır; kendi olaylarına göre ayarla.
$raw = file_get_contents('php://input', false, null, 0, 262145);
if ($raw === false || strlen($raw) > 262144) {
http_response_code(413);
exit;
}
$received = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
$expected = 'sha256='.hash_hmac('sha256', $raw, $secret);
if ($received === '' || !hash_equals($expected, $received)) {
http_response_code(403);
exit;
}
try {
$payload = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
http_response_code(400);
exit;
}
if (!is_array($payload)) {
http_response_code(400);
exit;
}
$event = $_SERVER['HTTP_X_GITHUB_EVENT'] ?? '';
if ($event !== 'ping') {
http_response_code(422); // Bu örnekte diğer olaylar işlenmiyor.
exit;
}
http_response_code(204);
hash_equals, karşılaştırmada PHP’nin zamanlamaya duyarlı güvenli karşılaştırma işlevini kullanır. Anahtarı depoya, URL’ye veya günlük dosyalarına yazmak yerine ortam değişkeni ya da uygun bir gizli anahtar yönetimiyle saklamalısın.
Örnekteki 256 KB sınırı evrensel bir GitHub sınırı değildir. Gerçek olayların boyutlarına göre PHP ve web sunucusu limitlerini birlikte ayarlamalısın; aksi hâlde geçerli bildirimleri de reddedebilirsin.
PHP-FPM kullanıyorsan ortam değişkeninin gerçekten PHP çalışanına ulaştığını da kontrol et. Bazı sunucu yapılandırmaları çalışanlara aktarılan ortam değişkenlerini sınırlar; panelde tanımlanmış olması uygulamada okunabildiği anlamına gelmeyebilir.
İmza doğruysa her şey tamam mı?
İmza doğrulaması, gövdenin gizli anahtarı bilen biri tarafından üretildiğini ve değiştirilmediğini doğrulamaya yardımcı olur. Yine de olay türünü, beklenen alanları ve iş kurallarını kontrol etmelisin. Örneğin gelen verinin hangi depoya veya hesaba ait olduğunu yalnızca beklenen alanlar üzerinden doğrulamadan işlem başlatmamalısın.
Bir başka ayrıntı da şudur: GitHub’ın bu imzası gövdeyi kapsar. X-GitHub-Event ve X-GitHub-Delivery gibi başlıkları tek başına imzalanmış veri sanmamalısın. Teslimat kimliği normal tekrar gönderimleri ayırt etmeye yardımcı olur; tek başına her türlü kötü niyetli yeniden oynatmaya karşı tam koruma sayılmamalıdır.
Tekrarlanan teslimatları nasıl yönetmeli?
Gerçek bir iş akışında, aynı bildirimin iki kez işlenmesi iki kez stok düşmeye veya iki e-posta gönderilmesine yol açabilir. GitHub’ın X-GitHub-Delivery değeri yeniden gönderimde aynı kalır. Bu değeri veritabanında benzersiz bir alanla kaydetmek, aynı teslimatın tekrar işlenmesini önlemeye yardımcı olabilir.
Ancak sadece “önce kaydet, sonra işlem yap” demek yeterli değil: Uygulama iki adım arasında kapanabilir. Bu yüzden bildirimi ve yapılacak işi kalıcı biçimde kaydetmeyi; ardından işleyiciyi yeniden çalıştırılabilir tasarlamayı düşün. Dış servis çağrılarında da mümkünse sağlayıcının desteklediği idempotency mekanizmasını kullan.
Hızlı cevap, güvenilir işlem
GitHub, webhook isteğine 10 saniye içinde 2xx yanıtı verilmesini öneriyor. Uzun süren işleri kuyrukta çalıştırmak bu nedenle faydalı olabilir. Fakat henüz hiçbir yere kaydedilmemiş önemli bir işi, sadece hızlı cevap vermek için başarılı kabul etme: Önce güvenilir biçimde teslim al, sonra arka planda işle. GitHub’ın webhook önerileri bu noktaları ayrıntılı anlatıyor.
Önemli bir fark daha: GitHub başarısız webhook teslimatlarını otomatik olarak yeniden göndermez. Başarısız teslimatları panelden inceleyebilir veya uygun API’lerle yeniden gönderim için ayrı bir mekanizma kurabilirsin. Diğer sağlayıcılarda tekrar deneme davranışı farklı olabilir.
Yayına almadan önce neleri denemeli?
Geçerli secret ile oluşturulan bir ping isteğinin başarılı döndüğünü kontrol et. Ardından gövdeyi veya imzayı değiştirip isteğin reddedildiğini doğrula. Secret eksikken sunucunun isteği kabul etmediğinden, olay türü desteklenmediğinde işlem yapılmadığından ve hassas verilerin loglara yazılmadığından emin ol.
Canlı ortamda HTTPS kullan, yalnızca ihtiyacın olan olaylara abone ol ve başarısız teslimatları izlemeyi unutma. Sağlam bir webhook entegrasyonu sadece isteği karşılamakla değil; doğrulamak, güvenilir biçimde teslim almak ve gerektiğinde sorunu takip edebilmekle tamamlanır.
BİRLİKTE ÖĞRENELİM
Yorumlar
Henüz yorum yok. Düşünceni paylaşarak sohbeti başlatabilirsin.
Bir Yorum Bırak.
Katkılarını ve deneyimlerini duymak isterim. Yorumlar yayınlanmadan önce incelenir.