Mehmet

WFN Üye
Konular
1,851
Mesajlar
1,852
Çözüm
0
Ticaret Puanı
0
Üyelik
1 Ay 6 Gün
Konum
YurtDışı
Web Sitesi
Var
Alanı
Google Adsense
1/1
Konu sahibi
HMAC kullanan webhook şemasında sağlayıcının tarif ettiği ham veri ve paylaşılan gizli anahtarla imza yeniden hesaplanıp sabit zamanlı karşılaştırılır. Her sağlayıcı HMAC kullanmaz; açık anahtarlı imza veya farklı doğrulama şeması varsa onun resmî yöntemini izleyin. Gövdeyi JSON nesnesine çevirip tekrar üretmeden önce doğrulamak gerekir; boşluk, karakter sırası veya satır sonu değişirse imza artık aynı olmayabilir. Doğrulama başarısızsa isteği işleme almadan 401 veya 403 yanıtı verin.

Doğrulama mantığı​


Webhook sağlayıcısı genellikle şu bilgileri verir:

  • İmza başlığının adı
  • İmzanın hex veya Base64 biçimi
  • HMAC algoritması; çoğunlukla SHA-256 gibi bir algoritma
  • HMAC hesabına girecek veri; yalnızca ham gövde veya zaman damgası ile birleştirilmiş gövde
  • İmzanın gizli anahtarla nasıl üretildiği
Sunucunuz aynı kurallarla beklenen imzayı üretir. Sağlayıcının gönderdiği değerle eşleşiyorsa gövde, gizli anahtarı bilen tarafça üretilmiş kabul edilir. Karşılaştırmayı normal === yerine sabit zamanlı karşılaştırmayla yapmak daha doğru bir uygulamadır.

Ham gövde neden önemlidir?​


{"a":1,"b":2} ile {"b":2,"a":1} aynı JSON anlamına gelebilir; ancak metin olarak farklıdır ve HMAC sonuçları da farklı olur. Bu nedenle istek gövdesini JSON olarak ayrıştırmadan önce bir kopyasını koruyun. İmzayı doğruladıktan sonra JSON ayrıştırma ve uygulama iş mantığına geçin.

Somut örnek: Node.js ile HMAC-SHA256​


Aşağıdaki örnek, varsayımsal bir sağlayıcının x-demo-signature başlığında sha256= ön ekiyle hex imza gönderdiğini kabul eder. İmza girdisi yalnızca ham HTTP gövdesidir. Gerçek sağlayıcı farklı bir başlık, ön ek veya zaman damgası kuralı kullanıyorsa bu kısımları kendi belgelerine göre değiştirin.

Kod:
const http = require('node:http');
const crypto = require('node:crypto');

const secret = process.env.WEBHOOK_SECRET;
if (!secret) {
  throw new Error('WEBHOOK_SECRET tanımlı değil');
}

const server = http.createServer((req, res) => {
  if (req.method !== 'POST' || req.url !== '/webhook') {
    res.writeHead(404);
    return res.end('Not found');
  }

  const chunks = [];
  let size = 0;
  const maxSize = 1024 * 1024;

  req.on('data', chunk => {
    size += chunk.length;
    if (size <= maxSize) chunks.push(chunk);
  });

  req.on('end', () => {
    if (size > maxSize) {
      res.writeHead(413);
      return res.end('Payload too large');
    }

    const rawBody = Buffer.concat(chunks);
    const received = req.headers['x-demo-signature'] || '';
    const prefix = 'sha256=';

    if (typeof received !== 'string' || !received.startsWith(prefix)) {
      res.writeHead(401);
      return res.end('Invalid signature');
    }

    const hex = received.slice(prefix.length);
    if (!/^[0-9a-f]{64}$/i.test(hex)) {
      res.writeHead(401);
      return res.end('Invalid signature');
    }

    const expected = crypto
      .createHmac('sha256', secret)
      .update(rawBody)
      .digest();
    const supplied = Buffer.from(hex, 'hex');
    const valid = supplied.length === expected.length &&
      crypto.timingSafeEqual(supplied, expected);

    if (!valid) {
      res.writeHead(401);
      return res.end('Invalid signature');
    }

    let event;
    try {
      event = JSON.parse(rawBody.toString('utf8'));
    } catch {
      res.writeHead(400);
      return res.end('Invalid JSON');
    }

    // Demo yalnız imza ve JSON doğrulamasını gösterir; üretim işi yoktur.
    res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
    res.end('Webhook accepted');
  });
});

server.listen(3000, '127.0.0.1', () => {
  console.log('Webhook dinleniyor: http://localhost:3000/webhook');
});

Bu örneği CommonJS için webhook.cjs adıyla kaydedin; type:module projesinde .js dosyasına require yapıştırmak hata verir. İstek gövdesi depolaması1MB ile sınırlı olsa da büyük/çok yavaş isteği girişte durdurmak için reverse proxy ve sunucu timeout/gövde sınırı ayrıca gerekir. Yerel demo yalnız loopback’te dinler; üretim TLS kurulumu değildir. Tüm gövdeyi veya sırrı loglamayın.

Çalıştırma ve test​


  1. Kodu webhook.cjs adıyla kaydedin.
  2. Aynı terminalde bir gizli anahtar belirleyin: export WEBHOOK_SECRET='ornek-gizli-anahtar'.
  3. Sunucuyu çalıştırın: node webhook.cjs.
  4. İkinci terminalde de aynı WEBHOOK_SECRET değerini tanımlayın; bir terminalin export işlemi önceden açılmış diğer terminale geçmez. Sonra sağlayıcıdaki kurala uygun imzayı üretip POST isteği gönderin. Bu varsayımsal örnekte Unix benzeri bir ortamda şu komut kullanılabilir: BODY='{"event":"invoice.created","id":"evt_123"}'; SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -r | awk '{print $1}'); curl -i -X POST http://localhost:3000/webhook -H "Content-Type: application/json" -H "X-Demo-Signature: sha256=$SIG" --data-binary "$BODY".

Başarılı istekte HTTP 200 ve Webhook accepted görülür; örnek yalnız doğrulama sonucunu döndürür, olayı kalıcı kaydetmez veya iş yürütmez. Gerçek webhook alıcısı olarak yayına almadan önce şema/olay yetkisi, kalıcı kuyruk, dedup ve hata politikası gerekir. Yanlış anahtar, değiştirilmiş gövde veya hatalı başlıkta HTTP 401 alınır. Geçerli imza taşıyan fakat bozuk JSON içeren istekte imza kontrolü geçer, ardından HTTP 400 döner; bu iki kontrolü birbirine karıştırmayın.

İmza sağlayıcının zaman damgasını da içeriyorsa, sağlayıcının tanımladığı biçimde timestamp + "." + rawBody gibi tam girdiyi HMAC’e verin. Zaman damgasını ayrıca kabul edilebilir süre içinde kontrol edin. Bu kontrol yeniden gönderilmiş eski bir isteğin tekrar işlenmesi riskini azaltır; ancak tek başına aynı olayın iki kez gelmesini önlemez. Olay kimliğini veya sağlayıcının teslimat kimliğini işlenmiş kayıtlarla karşılaştırarak işlemleri idempotent tasarlayın.

Karar tablosu​


Aşağıdaki ayrım, sağlayıcı belgesindeki imza tarifini kodunuza aktarırken hangi veriyi kullanacağınızı belirlemenize yardımcı olur.

Sağlayıcının tarifiHMAC girdisiUygulama kararı
Ham gövdeAyrıştırılmamış HTTP gövdesiÖnce doğrula, sonra JSON ayrıştır
Zaman damgası ve gövdeBelirtilen sırada birleştirilmiş metinBiçimi aynen koru ve zamanı ayrıca kontrol et
Base64 imzaSağlayıcının belirttiği algoritmaHex olarak değil Base64 çözerek karşılaştır

Sık yapılan hatalar​


  • JSON’u önce parse edip yeniden stringify etmek: Orijinal bayt dizisi korunmadığı için doğru imza yanlış görünebilir.
  • İmza başlığındaki sha256= veya benzeri ön eki hesaba katmak: Ön ekin imzaya dahil olup olmadığını sağlayıcı tanımına göre uygulayın; örnekte ön ek yalnızca taşımadır.
  • Gizli anahtarı istemci koduna, depoya veya hata yanıtına koymak: Anahtar yalnızca sunucu tarafında ve yapılandırma sırrı olarak tutulmalıdır.
  • Her geçerli isteği sınırsızca işlemek: Geçerli imza, olayın daha önce işlenmediğini garanti etmez; tekrar teslimatlar için idempotency kontrolü ekleyin.
  • İmza doğrulanmadan yan etki oluşturmak: Veritabanı güncellemesi, ödeme veya e-posta gibi işlemler yalnızca doğrulama ve gerekli veri kontrollerinden sonra yapılmalıdır.

Kaynaklar​



Sık sorulan sorular​


Webhook isteğini doğrulayamıyorsam ilk neyi kontrol etmeliyim?​

Önce doğru gizli anahtarın kullanıldığını, imza başlığının gerçekten alındığını ve ters vekil sunucunun isteğin gövdesini değiştirmediğini kontrol edin. Ardından sağlayıcının örnek imza girdisiyle kendi hesaplamanızı karşılaştırın; üretimde gizli anahtarı veya tam imzayı günlüklemeyin.

İmza doğrulandıktan sonra isteğe hemen 200 dönmeli miyim?​

Uzun süren işi webhook isteği içinde bekletmek yerine doğrulanan olayı güvenli bir kuyruğa veya kalıcı depoya yazıp hızlıca başarılı yanıt vermek daha uygun olabilir. Ancak kuyruk yazma işlemi başarısızsa başarı yanıtı vererek teslimatın kaybolmasına neden olmayın.
 

Sende şimdi bize katılmak ister misin?

Kayıt ol

Bize katılım kolay ve ücretsizdir!

Konuyu görüntüleyenler

Bu konuyu şu anda görüntüleyen üye bulunmuyor.