API belgeleri

Bildirim adresi kurun, imzayı doğrulayın

Yeni sızıntı ve tarama olaylarını sorgulamak yerine, olay oluştuğunda kendi adresinize alın. Her teslim imzalıdır; alıcınız imzayı doğrulayarak gövdenin Dark Radar’dan geldiğini ve yolda değişmediğini anlar.

Adresi ayarlayın

Adres bir koruma kaynağı grubuna bağlanır, grup başına bir tane. https ve herkese açık bir adres olmalı; iç ağ, localhost ve bulut meta veri adresleri reddedilir. Anahtarın webhooks:write kapsamı olmalı.

curl -X PUT "https://api.darkradar.com/api/public/v1/webhooks/64b1f0c2a9e4d21f8c3b9a10" \
  -H "x-api-key: $DARKRADAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://ornek.com.tr/darkradar-webhook","events":["leak.created","scan.failed"],"severities":["critical","high"]}'
İlk yanıt 201 ve signingSecret (whsec_…) içerir. Sır yalnız bu yanıtta gösterilir; güvenli bir yerde saklayın. Kaybederseniz POST /v1/webhooks/{groupId}/rotate-secret ile yenisini alın — eskisi hemen geçersiz olur.

İmza nasıl oluşur

Her teslimde X-DarkRadar-Signature: t=<unix saniye>,v1=<imza> başlığı gelir. İmza, t, bir nokta ve ham gövdenin birleşiminin sırrınızla HMAC-SHA256 özetidir (onaltılı).

Gövdeyi JSON olarak ayrıştırmadan önce, geldiği gibi alın. Ayrıştırıp yeniden yazılan gövdenin imzası tutmaz.
Karşılaştırmayı sabit zamanlı yapın (aşağıdaki örneklerdeki gibi).
t 5 dakikadan eskiyse reddedin; eski bir teslimin yeniden oynatılmasını önler.
X-DarkRadar-Event olay türünü, X-DarkRadar-Delivery gövdedeki id ile aynı teslim kimliğini taşır.

Node.js

import crypto from 'node:crypto';

// rawBody: raw request body (Buffer or string). header: X-DarkRadar-Signature.
export function darkradarSignatureValid(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(String(header ?? '').split(',').map((p) => p.split('=')).filter((p) => p.length === 2));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || !parts.v1 || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  const got = Buffer.from(parts.v1, 'hex');
  return got.length === expected.length && crypto.timingSafeEqual(got, expected);
}

// Express: app.post('/darkradar', express.raw({ type: 'application/json' }), (req, res) => {
//   if (!darkradarSignatureValid(req.body, req.get('X-DarkRadar-Signature'), process.env.DARKRADAR_WEBHOOK_SECRET)) return res.sendStatus(401);
//   const event = JSON.parse(req.body); /* deduplicate by event.id */ res.sendStatus(204);
// });

Python

import hashlib
import hmac
import time


# raw_body: raw request body (bytes). header: X-DarkRadar-Signature.
def darkradar_signature_valid(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in (header or "").split(",") if "=" in p)
    if not parts.get("t", "").isdigit() or "v1" not in parts:
        return False
    t = int(parts["t"])
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])


# Flask:
#   if not darkradar_signature_valid(request.get_data(), request.headers.get("X-DarkRadar-Signature", ""), os.environ["DARKRADAR_WEBHOOK_SECRET"]):
#       abort(401)
#   event = request.get_json()  # deduplicate by event["id"]

Ruby

require "openssl"

# raw_body: raw request body. header: X-DarkRadar-Signature.
def darkradar_signature_valid?(raw_body, header, secret, tolerance: 300)
  parts = header.to_s.split(",").map { |p| p.split("=", 2) }.select { |p| p.size == 2 }.to_h
  t = Integer(parts["t"], exception: false)
  return false if t.nil? || parts["v1"].nil? || (Time.now.to_i - t).abs > tolerance
  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{t}.#{raw_body}")
  OpenSSL.fixed_length_secure_compare(expected, parts["v1"])
rescue ArgumentError
  false
end

# Rails:
#   head :unauthorized unless darkradar_signature_valid?(request.raw_post, request.headers["X-DarkRadar-Signature"], ENV.fetch("DARKRADAR_WEBHOOK_SECRET"))
#   event = JSON.parse(request.raw_post) # deduplicate by event["id"]

PHP

<?php
// $rawBody: raw request body. $header: X-DarkRadar-Signature.
function darkradar_signature_valid(string $rawBody, string $header, string $secret, int $tolerance = 300): bool
{
    $parts = [];
    foreach (explode(',', $header) as $p) {
        $kv = explode('=', $p, 2);
        if (count($kv) === 2) {
            $parts[$kv[0]] = $kv[1];
        }
    }
    if (!isset($parts['t'], $parts['v1']) || !ctype_digit($parts['t'])) {
        return false;
    }
    $t = (int) $parts['t'];
    if (abs(time() - $t) > $tolerance) {
        return false;
    }
    $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
    return hash_equals($expected, $parts['v1']);
}

// Plain PHP:
//   $raw = file_get_contents('php://input');
//   if (!darkradar_signature_valid($raw, $_SERVER['HTTP_X_DARKRADAR_SIGNATURE'] ?? '', getenv('DARKRADAR_WEBHOOK_SECRET'))) { http_response_code(401); exit; }
//   $event = json_decode($raw, true); // deduplicate by $event['id']

Deneyin ve izleyin

Deneme teslimi imzalı bir webhook.test olayı gönderir ve alıcınızın yanıtını döndürür. Alıcı 2xx dönmeli; 10 saniye içinde yanıt vermeyen istek başarısız sayılır.

curl -X POST "https://api.darkradar.com/api/public/v1/webhooks/64b1f0c2a9e4d21f8c3b9a10/test" \
  -H "x-api-key: $DARKRADAR_API_KEY"

Geçici hatada (429, 5xx, ağ hatası) teslim hemen 3 kez denenir; olmazsa 1 dk, 5 dk, 30 dk, 2 sa ve 6 sa sonra yeniden denenir — alıcınız birkaç saat kapalı kalsa da olay kaybolmaz. 4xx yeniden denenmez. Aynı teslim birden çok kez gelebilir: gövdedeki id ile tekilleştirin. Son 100 teslim ve bekleyenler GET /v1/webhooks/{groupId}/deliveries ile görülür.

Bu sayfa işinize yaradı mı?