DokümantasyonBildirim kanalları ve webhook imzası

Dokümantasyon 07 / 10

Bildirim kanalları ve webhook imzası

Bildirimler, bir olay gerçekleştiğinde (ör. kesinti onaylandığında) seçtiğiniz kanallara gönderilir. Kullanılabilen kanal türleri ve aylık bildirim sayısı paketinize bağlıdır. Gönderimler kuyruk üzerinden yapılır ve geçici hatalarda yeniden denenir; gönderim geçmişini panelde Bildirimler sayfasında görebilirsiniz.

Olay türleri

Olay Ne zaman?
monitor.down Kesinti onaylandığında (ardışık hata eşiğine ulaşıldığında).
monitor.up Kesinti sona erdiğinde; kesinti süresiyle birlikte.
monitor.flapping Son 60 dakikada 4 veya daha fazla onaylı durum değişikliği olduğunda. Sonraki 30 dakika tekrarlayan bildirimler bastırılır.
incident.created, incident.updated, incident.resolved Status sayfası olaylarında.
maintenance.scheduled, .started, .completed, .canceled, .reminder Planlı bakımlarda.
target.verification_lost Hedef doğrulaması düştüğünde.
domain.tls_expiring Özel domain sertifikası yenilenemediğinde.
channel.test Paneldeki “Test” düğmesiyle.

Bildirimlerde hedef adres maskelenir; parola, token ve header değerleri gibi sırlar hiçbir mesaja eklenmez.

Kanallar

  • E-posta: Başlık, açıklama, alanlar ve panel bağlantısı içeren HTML + düz metin e-posta.
  • Telegram: Bot üzerinden sohbet veya gruba mesaj.
  • Slack: Gelen webhook (incoming webhook) adresine düz metin mesaj.
  • Discord: Webhook adresine embed mesaj; kullanıcı/rol etiketlemeleri devre dışıdır.
  • Genel webhook: JSON gövdeli, HMAC-SHA256 ile imzalanmış POST isteği.

Webhook adresleri yalnızca https:// ile başlayabilir (şifresiz http:// kabul edilmez) ve izleme hedefleriyle aynı ağ güvenlik politikasından geçer: özel/ayrılmış IP adreslerine gönderim yapılmaz, yönlendirme izlenmez, zaman aşımı 10 saniyedir. Alıcı 404 veya 410 dönerse gönderim yeniden denenmez.

Genel webhook biçimi

İstek başlıkları:

Content-Type: application/json
X-Isletme-Event: monitor.down
X-Isletme-Event-Id: 3f0c6f7e-...-uuid
X-Isletme-Signature: t=1767225600,v1=5d41402abc4b2a76b9719d911017c592...

Gövde:

{
  "id": "3f0c6f7e-...-uuid",
  "type": "monitor.down",
  "created_at": "2026-01-01T00:00:00+00:00",
  "title": "Kesinti: Ana site",
  "message": "…",
  "severity": "critical",
  "url": "https://…/app/o/…/monitorler/…",
  "data": { "…": "olaya özgü alanlar" }
}

severity değerleri: critical, warning, ok, info.

İmza doğrulama

X-Isletme-Signature başlığı virgülle ayrılmış parçalardan oluşur:

  • t=<unix zaman damgası> — gönderim zamanı (saniye).
  • v1=<hex> — HMAC_SHA256(secret, t + "." + ham_gövde) değerinin küçük harfli onaltılık gösterimi.
  • Secret yenilendikten sonraki geçiş döneminde ikinci bir v1= parçası, önceki secret ile hesaplanmış imzayı taşır. Herhangi bir v1 eşleşirse imza geçerlidir.

Önerilen doğrulama adımları:

  1. Gövdeyi ayrıştırmadan önce ham bayt olarak okuyun.
  2. t değerinin şimdiki zamandan en fazla birkaç dakika farklı olduğunu kontrol edin (replay koruması; ör. 5 dakika).
  3. İmzayı kendiniz hesaplayıp sabit zamanlı karşılaştırma ile v1 değerleriyle karşılaştırın.
  4. X-Isletme-Event-Id değerini saklayarak aynı olayın tekrar işlenmesini önleyin (yeniden denemelerde aynı kimlik gönderilir).

PHP örneği

$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_ISLETME_SIGNATURE'] ?? '';
$parts = [];
foreach (explode(',', $header) as $p) {
    [$k, $v] = array_pad(explode('=', $p, 2), 2, '');
    $parts[$k][] = $v;
}
$t = (int) ($parts['t'][0] ?? 0);
if (abs(time() - $t) > 300) { http_response_code(400); exit; }
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
$ok = false;
foreach ($parts['v1'] ?? [] as $sig) {
    $ok = $ok || hash_equals($expected, $sig);
}
if (! $ok) { http_response_code(401); exit; }

Node.js örneği

const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const parts = header.split(',').map(p => p.split('='));
  const t = Number((parts.find(([k]) => k === 't') || [])[1]);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return parts.filter(([k]) => k === 'v1').some(([, v]) =>
    v.length === expected.length && crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected)));
}

Webhook'unuz 2xx dışında bir yanıt verirse gönderim başarısız sayılır ve yeniden denenir.

Bu belge ürünün mevcut davranışını anlatır. Bir tutarsızlık görürseniz lütfen bize bildirin.