← blog · 14 Eylül 2026

Webhook Tasarımı: Yeniden Deneme, Idempotency ve İmza Doğrulama

Webhook'lar basit görünür ama üretimde üç soru işi karmaşıklaştırır: teslimat garantisi, yinelenen olaylar ve gönderenin kimliği. Bu yazı retry stratejisi, idempotency anahtarı ve imza doğrulamasını pratik örneklerle ele alır.

Bir sistemin başka bir sisteme "bir şey oldu" demesinin iki yolu vardır: karşı taraf sizi düzenli aralıklarla sorar (polling) ya da siz olay olduğunda ona haber verirsiniz (webhook). Polling basittir ama gecikme ve gereksiz yük getirir; sorgulama sıklığını artırırsanız sunucuyu yorarsınız, azaltırsanız olay geç fark edilir. Webhook bu dengeyi tersine çevirir: olay anında bildirim gider, ama artık siz bir uç nokta işletmek, gelen isteği doğrulamak ve başarısız teslimatları yönetmek zorunda kalırsınız.

Webhook kulağa basit gelir: "olay olunca şu adrese POST at." Üretimde karşılaşılan üç soru bu basitliği bozar: teslimat garantisi ne olacak, aynı olay iki kez gelirse ne olur, isteğin gerçekten beklenen kaynaktan geldiğini alıcı nasıl bilir. Bu üç soruya cevap vermeden yazılan bir entegrasyon, ilk trafik artışında ya olay kaybeder ya da aynı olayı birden fazla kez işler.

Teslimat garantisi: en az bir kez mi, en çok bir kez mi

İki temel model var. En çok bir kez teslimat, gönderen bir kere dener ve başarısız olursa vazgeçer; basittir ama alıcı tarafta geçici bir ağ sorunu ya da beş saniyelik bir bakım penceresi, olayın tamamen kaybolması demektir. En az bir kez teslimat, gönderen başarılı bir HTTP 2xx alana kadar (ya da bir deneme sınırına kadar) yeniden dener; olay kaybolmaz ama aynı olayın birden fazla kez gelmesi normal ve beklenen bir durum haline gelir.

Üretimde neredeyse her zaman en az bir kez teslimat tercih edilmeli. Kaybolan bir ödeme bildirimi, kaybolan bir sipariş güncellemesinden çok daha pahalıya patlar. Ama bu seçim bedelsiz değil: alıcı tarafın idempotent olması artık isteğe bağlı değil, zorunlu bir gereksinim haline gelir.

Yeniden deneme yaparken sabit aralıklarla denemek (örneğin her 30 saniyede bir) iki sorun çıkarır. Alıcı sistem zaten aşırı yüklüyken sabit aralıklı istekler onu bitirir, ve tüm gönderenlerin denemeleri aynı ana denk gelirse (thundering herd) yük anlık olarak katlanır. Bunun yerine üstel geri çekilme (exponential backoff) ve rastgele gecikme (jitter) birlikte kullanılır: ilk denemeden sonra 1 saniye bekle, başarısızsa 2, sonra 4, diye ikiye katlanarak devam et, üst sınır koy (örneğin 1 saat), her bekleme süresine yüzde 10-20 rastgele sapma ekle ki tüm istemciler aynı anda tekrar denemesin. Toplam deneme süresi için de bir tavan gerekir; sonsuza kadar denemek yerine 24-72 saat sonra pes edip olayı bir "ölü mektup" kuyruğuna düşürmek, hem kaynak israfını önler hem de sorunun fark edilmesini sağlar.

Idempotency: aynı olay iki kez işlenirse ne olur

En az bir kez teslimatın doğal sonucu tekrarlanan isteklerdir. Alıcı tarafta bunun çözümü genellikle iki parçadan oluşur: her olayın benzersiz bir kimliği (event id) olmalı, ve alıcı bu kimliği daha önce işleyip işlemediğini kontrol edebilmelidir.

En basit uygulama, işlenen olay kimliklerini bir tabloda tutmaktır: istek gelince önce kimlik tabloda var mı diye bakılır, varsa 200 döndürülüp hiçbir şey yapılmaz, yoksa işlem yapılır ve kimlik aynı veritabanı işlemi (transaction) içinde tabloya yazılır. Bu son nokta önemlidir: iş mantığını çalıştırıp kimliği ayrı bir adımda kaydederseniz, ikisi arasında sunucu çökerse aynı olay bir sonraki denemede tekrar işlenir. Kayıt tutma süresi de düşünülmeli; olayları sonsuza kadar saklamak yerine gönderenin en uzun yeniden deneme penceresi kadar (yukarıdaki örnekte 72 saat) saklamak yeterlidir.

Bazı sistemler event id yerine (ya da onunla birlikte) istemcinin ürettiği bir "idempotency key" ister; bu, aynı mantıksal işlemin (örneğin bir ödeme talebi) yeniden gönderilmesi istemci tarafından başlatıldığında da güvenli olmasını sağlar. İki kavram karışmasın: event id sunucunun ürettiği ve tekilliği garanti ettiği bir alan, idempotency key ise istemcinin gönderdiği ve sunucunun tekrar eden isteği tanımasını sağlayan bir alandır. Bir webhook sisteminde genellikle sadece event id yeterlidir çünkü olayı üreten taraf sizsiniz.

İmza doğrulama: isteğin gerçekten sizden geldiğini nasıl bilecekler

Webhook uç noktası dışa açık bir HTTP adresidir. Kimlik doğrulaması olmayan bir uç nokta, adresi öğrenen herkesin sahte "ödeme tamamlandı" ya da "kullanıcı silindi" olayları göndermesine izin verir. Çözüm, her isteğe gönderen tarafından hesaplanan bir imza eklemek ve alıcının bu imzayı kendi hesapladığı değerle karşılaştırmasıdır.

Yaygın yöntem HMAC-SHA256'dır: gönderen ve alıcı paylaşılan bir gizli anahtarı (secret) önceden anlaşır, gönderen istek gövdesinin (ve genellikle bir zaman damgasının) HMAC değerini hesaplayıp bir başlıkta (örneğin X-Signature) yollar, alıcı aynı hesaplamayı kendi tarafında yapıp iki değeri karşılaştırır. Node.js'te bu doğrulama şöyle görünür:

const crypto = require('crypto');

function verifySignature(payload, signatureHeader, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

Burada iki ayrıntı kritik. Birincisi, karşılaştırma === ile değil timingSafeEqual ile yapılmalı; normal string karşılaştırma karakter karakter kısa devre yaptığı için ne kadar sürdüğü, doğru imzanın ilk kaç karakterini tuttuğunuza bağlı olur, bu da zamanlama saldırısına (timing attack) kapı açar. İkincisi, gövde ham (raw) haliyle imzalanmalı; JSON'u parse edip tekrar serialize ederek karşılaştırma yapmayın, çünkü anahtar sırası ya da boşluk farkı imzayı bambaşka bir değere çevirir ve bu genellikle "bazı isteklerde çalışıyor bazılarında çalışmıyor" şeklinde tuhaf bir hataya dönüşür.

Zaman damgasını imzaya dahil etmek ayrı bir sorunu çözer: replay saldırısı. İmza doğru olsa bile, birisi eskiden yakalanmış geçerli bir isteği tekrar gönderirse alıcı bunu meşru sanır. İmzaya bir zaman damgası eklenip alıcı tarafta bu damganın örneğin son 5 dakika içinde olup olmadığı kontrol edilirse, eski bir isteğin tekrar oynatılması engellenir.

Sık düşülen tuzaklar

Dört yüz seviyesindeki hatalarda (400, 401, 422) sonsuza kadar yeniden denemek yaygın bir hatadır. Bu hatalar genellikle isteğin kendisinde bir sorun olduğunu gösterir (yanlış format, geçersiz imza) ve tekrar denemek sorunu çözmez, sadece kaynak israf eder. Yeniden deneme mantığı 5xx sunucu hataları ve zaman aşımları için ayrılmalı, 4xx hatalarında olay doğrudan ölü mektup kuyruğuna düşürülmeli.

Alıcı tarafta işlem süresi uzunsa (örneğin gelen olayı işlerken ağır bir hesaplama ya da üçüncü taraf çağrısı yapılıyorsa) webhook isteğini senkron olarak tam işleyip sonra 200 dönmek riskli bir tasarımdır. Gönderen taraf genellikle birkaç saniyelik bir zaman aşımı uygular; bu süre aşılırsa istek başarısız sayılıp tekrar gönderilir, siz de aynı olayı hem ilk hem ikinci istekte işlemiş olursunuz. Doğru desen, isteği alır almaz olayı bir kuyruğa yazıp hemen 200 dönmek, asıl işi arka planda yapmaktır.

Gizli anahtar rotasyonu da gözden kaçan bir noktadır. Anahtar değiştirildiğinde eski anahtarla imzalanmış, henüz teslim edilmemiş isteklerin ne olacağı düşünülmeli; genellikle bir geçiş penceresinde her iki anahtarın da kabul edilmesi gerekir, aksi halde rotasyon anında bir grup olay kalıcı olarak reddedilir.

Son olarak, sıralama garantisi beklememek gerekir. Ağ katmanındaki gecikmeler ve yeniden denemeler yüzünden olaylar üretildikleri sırayla varmayabilir. Sıra önemliyse (örneğin bir kaydın "oluşturuldu" olayının "güncellendi" olayından önce işlenmesi gerekiyorsa) her olaya bir sıra numarası ya da zaman damgası eklenmeli ve alıcı, kendisinden daha eski bir olay geldiğinde bunu göz ardı edebilmeli ya da sıraya koyabilmelidir.

Ne zaman webhook seçilmemeli

Alıcı sistem her zaman erişilebilir bir HTTP uç noktası işletemiyorsa (masaüstü uygulaması, mobil istemci, kapalı bir kurumsal ağın arkasındaki sistem) webhook doğal bir uyum değildir; bu durumlarda polling ya da istemcinin bir kuyruğa bağlanıp mesaj çektiği bir model daha uygundur. Aynı şekilde, çok sayıda tüketicinin aynı olay akışını farklı hızlarda işlemesi, olayları yeniden oynatabilmesi ya da uzun süre saklayabilmesi gerekiyorsa, webhook yerine Kafka gibi bir olay günlüğü (event log) daha doğru araçtır; webhook doğası gereği "ateşle ve unut"a yakındır, kalıcı bir geçmiş sunmaz. Webhook, tek bir tüketicinin neredeyse gerçek zamanlı bir bildirime ihtiyaç duyduğu ve tüketicinin genellikle erişilebilir olduğu durumlar için doğru araçtır; bu şartlar sağlanmıyorsa başka bir desen aramak daha az baş ağrısı yaratır.