Başlarken
Webhook'lar

Webhook'lar

Webhook, bizim sizin sunucunuza yaptığımız bir HTTP çağrısıdır. Bir ödeme sonuçlandığında, bir cüzdan transferi geri alındığında ya da bir fraud kararı üretildiğinde, olayı kayıtlı endpoint'inize göndeririz.

Teslimat ayrı bir servis tarafından yürütülür: her teslimat kalıcı olarak kaydedilir, yeniden denenir, denemeleri tek tek loglanır ve panelden yeniden oynatılabilir.

Bir teslimat neye benzer

POST /webhooks/lunixi HTTP/1.1
content-type: application/json
user-agent: Lunixi-Webhook-Service/1.0
x-lunixi-event-id: evt_01J8XQ...
x-lunixi-event-type: payment.captured
x-lunixi-signature: v2=9f2c1a…
x-lunixi-signature-timestamp: 2026-08-17T09:31:44.118Z
Başlıkİçerik
x-lunixi-event-idOlayın tekil kimliği — idempotenslik anahtarınız
x-lunixi-event-typeOlay tipi (ör. payment.captured)
x-lunixi-signatureVirgülle ayrılmış imza listesi: bir veya iki v2=<hex> öğesi (ikincisi yalnız secret rotasyonunun 24 saatlik grace penceresinde bulunur)
x-lunixi-signature-timestampİmzanın üretildiği an, ISO 8601

2xx dönerseniz teslimat başarılı sayılır. Başka her durum kodu yeniden denemeyi tetikler.

⚠️

Yönlendirme takip edilmez. İstek maxRedirects: 0 ile gönderilir; 301/302 dönerseniz teslimat başarısız sayılır. Endpoint'iniz son adres olmalıdır.

İmza doğrulama

v2 imzası ham gövdenin HMAC'idir — gövdeyi parse etmeden doğrularsınız. İmzalanan metin şudur:

{eventId}.{timestamp}.{rawBody}
  • eventId — x-lunixi-event-id başlığı (gövdedeki id ile aynı)
  • timestamp — x-lunixi-signature-timestamp başlığının birebir string değeri (ISO 8601)
  • rawBody — isteğin ham gövde byte'ları (UTF-8) — parse yok, yeniden serileştirme yok, kanonikleştirme yok

x-lunixi-signature başlığı virgülle ayrılmış bir veya iki v2=<hex> öğesi taşır. Doğrulama: başlığı , üzerinden bölün, her v2= öğesini ham gövde üzerinden hesapladığınız HMAC ile sabit zamanlı karşılaştırın — herhangi biri eşleşirse imza geçerlidir. (İkinci v2= öğesi yalnız secret rotasyonu sırasında bulunur; aşağıya bakın.)

import crypto from 'crypto';
 
export function verifyLunixiWebhook(
  rawBody: string,               // ham gövde — parse ETMEDEN
  headers: Record<string, string>,
  signingSecret: string,
  toleranceSeconds = 300,
): boolean {
  const eventId = headers['x-lunixi-event-id'];
  const timestamp = headers['x-lunixi-signature-timestamp'];
  if (Math.abs(Date.now() - Date.parse(timestamp)) > toleranceSeconds * 1000) return false;
 
  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(`${eventId}.${timestamp}.${rawBody}`)
    .digest('hex');
 
  return (headers['x-lunixi-signature'] ?? '')
    .split(',')
    .map((item) => item.trim())
    .filter((item) => item.startsWith('v2='))
    .some((item) => {
      const received = Buffer.from(item.slice(3), 'hex');
      const wanted = Buffer.from(expected, 'hex');
      return received.length === wanted.length && crypto.timingSafeEqual(received, wanted);
    });
}
⚠️

Uzunlukları eşit değilken timingSafeEqual istisna fırlatır. Önce uzunlukları karşılaştırın, sonra çağırın — yukarıdaki örnek bunu yapıyor.

x-lunixi-signature-timestamp tazeliğini kendiniz kontrol etmelisiniz (önerilen tolerans ≤ 300 saniye); böylece yakalanmış bir isteğin sonradan tekrar oynatılması (replay) işe yaramaz. Yukarıdaki örnek bunu da yapıyor.

Secret rotasyonu

Secret'ınızı döndürdükten sonra teslimatlar 24 saat boyunca hem yeni hem eski secret'la çift imzalanır ("grace penceresi"): başlıkta iki v2= öğesi bulunur — önce yeni secret'ınki, sonra eskisininki (v2=<hexYeni>,v2=<hexEski>). Herhangi bir v2= öğesi eşleştiğinde imzayı geçerli sayan bir doğrulayıcı, rotasyonu sıfır kesintiyle atlatır — yukarıdaki örnek böyle çalışır. 24 saat sonra eski secret kullanımdan çıkar.

Yeniden deneme politikası

Başarısız teslimat sabit bir cetvele göre yeniden denenir; her bekleme değerine ±%20 jitter uygulanır:

DenemeBeklemeDenemeBekleme
110 sn71 sa
230 sn82 sa
32 dk94 sa
45 dk108 sa
515 dk1112 sa
630 dk1224 sa
  • Toplam 12 deneme (~52 saatlik ufuk); sonrasında teslimat DEAD_LETTER olur.
  • DEAD_LETTER bir teslimatı yeniden oynatmak deneme bütçesini sıfırlar — taze 12 deneme hakkıyla kuyruğa döner.
  • İstek zaman aşımı varsayılan 10 saniye (1–30 sn aralığına sıkıştırılır).
  • Bu sınırlar kurulum bazında yapılandırılabilir; on-prem kurulumda operatörünüze sorun.

x-lunixi-event-id ile idempotent olun. Yeniden deneme, endpoint'inizin 2xx dönmediği her durumda çalışır — siz olayı işlemiş ama yanıt yolda kaybolmuş olsa bile. Aynı eventId'yi ikinci kez gördüğünüzde işlemi tekrarlamayın.

Teslimat durumları

DurumAnlamı
PENDINGKuyruğa alındı, henüz denenmedi
PROCESSINGŞu anda gönderiliyor
DELIVEREDEndpoint 2xx döndü
FAILEDDeneme başarısız, yeniden denenecek
DEAD_LETTERDeneme hakkı tükendi — otomatik denenmez

DEAD_LETTER olan bir teslimat kaybolmaz; panelden veya API'den yeniden oynatılabilir. → Webhook Kurulumu

Endpoint'in otomatik kapatılması

Bir endpoint üst üste başarısız olmaya devam ederse (varsayılan eşik 20), durumu FAILED yapılır ve teslimat almayı durdurur.

⚠️

Bu sessiz bir durum değildir ama kendiliğinden düzelmez. Endpoint'inizi onardıktan sonra panelden yeniden etkinleştirmeniz gerekir. Sunucunuzu bakıma alacaksanız, uzun süreli 5xx yerine hızlıca 2xx dönüp işi kendi kuyruğunuza almayı düşünün.

Güvenlik: nereye çağrı yapılır

Teslimat servisi, kayıt anında değil, çağrı anında hedef adresi yeniden doğrular:

  • Şema kontrolü — yalnız http/https
  • Adres çözümlendikten sonra IP sınıfı kontrolü — loopback (127.0.0.0/8, ::1), özel ağlar (10/8, 172.16/12, 192.168/16) ve bağlantı-yerel adresler reddedilir
  • Soket doğrulanmış IP'ye sabitlenir — kayıt anıyla bağlantı anı arasındaki DNS yeniden bağlama (rebinding) penceresi kapatılır

Yani endpoint'iniz genel internetten erişilebilir olmalıdır. Yerel geliştirmede bir tünel (ngrok vb.) kullanın.

Hangi ürünler olay üretir

Webhook omurgası dokuz kaynaktan besleniyor: organization · payment · subscription · invoice · usage · kyc · fraud · wallet · sanction.

Abonelik olay tipi bazındadır — bir endpoint'i payment.captured'a abone edip diğerini wallet.transfer.reversed'a abone edebilirsiniz. Ürün bazında olay listeleri için ilgili ürünün sayfasına bakın.

Banka hesabı (bank_account.*) ve banka transferi (bank_transfer.*) olayları BANK_CONNECT ürününün endpoint'lerine teslim edilir. → Banka Hesapları › Webhook Olayları

Sigorta olayları (insurance.*) INSURANCE_SERVICE ürününün endpoint'lerine teslim edilir. → Sigorta › Webhook Olayları

Sıradaki adım

Endpoint oluşturma, olay aboneliği, secret döndürme ve yeniden oynatma: Webhook Kurulumu