API sənədləri

Bildiriş ünvanı qurun, imzanı doğrulayın

Yeni sızma və tarama hadisələrini sorğulamaq əvəzinə öz ünvanınıza dərhal alın. Hər çatdırılma imzalıdır; qəbuledici imzanı doğrulayaraq gövdənin Dark Radar-dan gəldiyini və yolda dəyişmədiyini anlayır.

Ünvanı tənzimləyin

Ünvan bir qoruma qaynaq qrupuna bağlanır, qrup başına bir dənə. https və hamıya açıq ünvan olmalıdır; daxili şəbəkə, localhost və bulud meta məlumat ünvanları rədd edilir. Açarın webhooks:write əhatəsi olmalıdır.

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 cavab 201 və signingSecret (whsec_…) daşıyır. Sirr yalnız bu cavabda göstərilir; təhlükəsiz yerdə saxlayın. İtirsəniz POST /v1/webhooks/{groupId}/rotate-secret ilə yenisini alın — köhnəsi dərhal etibarsız olur.

İmza necə yaranır

Hər çatdırılmada X-DarkRadar-Signature: t=<unix saniyə>,v1=<imza> başlığı gəlir. İmza t, bir nöqtə və xam gövdənin birləşməsinin sirrinizlə HMAC-SHA256 heşidir (onaltılıq).

Gövdəni JSON kimi təhlil etməzdən əvvəl, gəldiyi kimi alın. Təhlil edilib yenidən yazılan gövdənin imzası uyğun gəlmir.
Müqayisəni sabit zamanlı edin (aşağıdakı nümunələrdəki kimi).
t 5 dəqiqədən köhnədirsə rədd edin; köhnə çatdırılmanın yenidən oynadılmasının qarşısını alır.
X-DarkRadar-Event hadisə növünü, X-DarkRadar-Delivery gövdədəki id ilə eyni çatdırılma kimliyini daşıyı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']

Sınayın və izləyin

Sınaq çatdırılması imzalı bir webhook.test hadisəsi göndərir və qəbuledicinizin cavabını qaytarır. Qəbuledici 2xx qaytarmalıdır; 10 saniyə ərzində cavab verməyən sorğu uğursuz sayılır.

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

Müvəqqəti xətada (429, 5xx, şəbəkə xətası) çatdırılma dərhal 3 dəfə sınanır; alınmazsa 1 dəq, 5 dəq, 30 dəq, 2 saat və 6 saat sonra yenidən sınanır — qəbulediciniz bir neçə saat bağlı qalsa da hadisə itmir. 4xx yenidən sınanmır. Eyni çatdırılma birdən çox dəfə gələ bilər: gövdədəki id ilə təkrarı ayırın. Son 100 çatdırılma və gözləyənlər GET /v1/webhooks/{groupId}/deliveries ilə görünür.

Bu səhifə işinizə yaradı?