ترويسات الطلب المطلوبة

كل طلب (باستثناء /v1/health) يجب أن يحمل هذه الترويسات الأربع. في حال غياب أو عدم صلاحية أي منها، يرفض الخادم الطلب بـ HTTP 401.

HeaderFormatDescription
KH-Keykh_live_[A-Z0-9]{32}معرّف المفتاح العام. ليس سراً، ويجوز ظهوره في السجلات.
KH-Timestampثوانٍ Unix (10 خانات)الطابع الزمني الحالي. نافذة التسامح ±300 ثانية.
KH-Nonce22-44 حرف base64urlقيمة أحادية الاستخدام لكل طلب. تُخزَّن لمدة 600 ثانية ثم يجوز إعادة استخدامها.
KH-Signature64 hexHMAC-SHA256(السر، سلسلة التوقيع)، مُرمَّزة كـ hex.

بناء سلسلة التوقيع

تتكوّن السلسلة المراد توقيعها من خمسة مكونات مفصولة بسطر جديد (\n).

signing_string = METHOD + "\n"
               + PATH    + "\n"
               + TIMESTAMP + "\n"
               + NONCE   + "\n"
               + SHA256_HEX(BODY)

signature = HEX( HMAC_SHA256(secret, signing_string) )

يشمل PATH مسار الطلب الكامل بما فيه سلسلة الاستعلام، دون المضيف أو الجزء. أما BODY فهو بايتات الجسم الخام؛ ولطلبات GET/DELETE بلا جسم تكون BODY سلسلة فارغة قيمتها SHA256 الثابت المعروف e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

تطبيقات مرجعية

PHP:

$method = 'POST';
$path   = '/v1/orders';
$body   = json_encode(['product_id' => 42, 'billing_cycle' => 'monthly']);
$ts     = (string) time();
$nonce  = bin2hex(random_bytes(16));

$signing = "{$method}\n{$path}\n{$ts}\n{$nonce}\n" . hash('sha256', $body);
$sig = hash_hmac('sha256', $signing, $KH_SECRET);

$ch = curl_init('https://www.kernelhost.com/cp/kh_reseller_api/v1/orders');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'POST',
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        "KH-Key: {$KH_KEY}",
        "KH-Timestamp: {$ts}",
        "KH-Nonce: {$nonce}",
        "KH-Signature: {$sig}",
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
]);
$res = curl_exec($ch);

Node.js:

import crypto from 'crypto';

const method = 'POST';
const path   = '/v1/orders';
const body   = JSON.stringify({ product_id: 42, billing_cycle: 'monthly' });
const ts     = Math.floor(Date.now() / 1000).toString();
const nonce  = crypto.randomBytes(16).toString('hex');

const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const signing  = `${method}\n${path}\n${ts}\n${nonce}\n${bodyHash}`;
const sig      = crypto.createHmac('sha256', KH_SECRET).update(signing).digest('hex');

const res = await fetch('https://www.kernelhost.com/cp/kh_reseller_api/v1/orders', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'KH-Key': KH_KEY,
        'KH-Timestamp': ts,
        'KH-Nonce': nonce,
        'KH-Signature': sig,
        'Idempotency-Key': crypto.randomBytes(16).toString('hex'),
    },
    body,
});

الحماية من إعادة الإرسال

لا يمكن إعادة إرسال طلب موقّع بنجاح. يحفظ الخادم كل Nonce لمدة 600 ثانية في قاعدة البيانات؛ ويُرفَض أي طلب ثانٍ بنفس الـ Nonce برسالة replay_detected. كذلك يُرفَض أي طلب يبتعد طابعه الزمني أكثر من 300 ثانية عن وقت الخادم.

نموذج النطاقات

لكل مفتاح API قائمة نطاقات صريحة. تتحقق المسارات من النطاق المطلوب؛ وإن كان مفقوداً، يُرَد HTTP 403 forbidden_scope. يجب تفعيل النطاقات الكتابية والحساسة صراحةً عند إنشاء مفتاح API.

  • read:products, read:orders, read:services, read:billing, read:webhooks
  • read:credentials (قراءة بيانات اعتماد الخدمات (كلمات مرور الجذر، FTP، VNC). تُنتج إدخال تدقيق إضافياً credentials.read لكل استدعاء.)
  • write:orders (إنشاء الطلبات ودفعها.)
  • write:services (تنفيذ إجراءات الخدمة (تشغيل/إيقاف/إعادة تشغيل/إعادة تثبيت/إنهاء).)
  • write:webhooks (تعيين عنوان Webhook.)