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"]}'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).
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.