API Təhlükəsizliyi
Buluthat API-si telefon santralınızı idarə edir: zəng başladır, zəngi bağlayır, SMS göndərir, müştəri nömrələrini emal edir. Buna görə açarın sızması "hesabat görünür" yox, "hesabınızdan zəng edilir" deməkdir. Bu səhifə açarınızı necə qoruyacağınızı və Buluthat-ın sizin əvəzinizə hansı qorumaları tətbiq etdiyini izah edir.
Əsas ünvan: https://api.buluthat.com/api/
Qatlar bir baxışda
Hər sorğu ardıcıllıqla bu qapılardan keçir. Biri rədd edərsə sorğu emal olunmur və jurnala yazılır.
| Sıra | Qapı | Nə edir | Xəta |
|---|---|---|---|
| 1 | Kobud qüvvə kilidi | Bir IP 10 dəqiqədə 20 dəfə yanlış açar ya da imza sınayarsa həmin IP müvəqqəti kilidlənir | 429 ip_locked |
| 2 | Gövdə limiti | 5 MB-dan böyük sorğu gövdəsi oxunmur | 413 payload_too_large |
| 3 | Açar | Açar yalnız başlıqda qəbul edilir; sistemdə yalnız SHA-256 xülasəsi saxlanılır | 401 invalid_token |
| 4 | Müddət və ləğv | Müddəti bitmiş ya da ləğv edilmiş açar rədd edilir | 401 token_expired |
| 5 | İcazəli IP | Açara IP siyahısı təyin edilibsə yalnız həmin ünvanlardan gələn sorğu keçir | 403 ip_not_allowed |
| 6 | Hesabın vəziyyəti | Bağlanmış ya da passiv hesabın açarları işləmir | 403 account_inactive |
| 7 | Əhatə | Açar yalnız icazə verilən API-lərə daxil olur | 403 scope_denied |
| 8 | İmza | Açarda "imzalı sorğu məcburidir" açıqdırsa HMAC imzası, vaxt damğası və birdəfəlik nonce doğrulanır | 401 signature_* |
| 9 | Sürət limiti | Açar başına və hesab cəmində dəqiqəlik limit | 429 rate_limited |
Sürətli başlanğıc
- Paneldə Hesab və Dəstək > API Açarları səhifəsini açın (yalnız hesab səlahiyyətlisi görür).
- Yeni açar: ad verin, yalnız lazım olan səlahiyyətləri işarələyin, serverinizin çıxış IP-sini yazın, İmzalı sorğu məcburidir-nu açın.
- Ekranda bir dəfə göstərilən iki dəyəri kopyalayın: API açarı (
bt_…, hər sorğudaAuthorizationbaşlığında gedir) və imza sirri (bts_…, sorğunu imzalamaq üçün istifadə olunur, heç vaxt göndərilmir). - Bağlantını yoxlayın:
curl "https://api.buluthat.com/api/autocall.php?action=ping" \
-H "Authorization: Bearer bt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
İmza məcburidirsə bu sorğu 401 signature_required qaytarır; aşağıdakı Sorğu imzalama bölməsindəki nümunələrdən birini istifadə edin.
Açar və imza sirri sistemdə geri oxuna bilən formada saxlanılmır. İtirsəniz bərpa edə bilmərik; paneldən açarı yeniləyirsiniz.
Əhatələr
Hər açar bir və ya bir neçə əhatə ilə yaradılır. Əhatədən kənar API-yə gələn sorğu 403 scope_denied qaytarır.
| Əhatə | Açdığı API-lər |
|---|---|
autocall | Avtomatik Zəng (api/autocall.php). Köhnə inteqrasiyalarla uyğunluq üçün səsli asistent, səsli doğrulama və zəng idarəetmə nöqtələrini də açır |
call | Zəng idarəetməsi və növbələr: begin_call, bridge, hangup, transfer, mute, queues, queues_pending, queue_user_list, queue_manage_users, agent_statuses |
voicebot | Səsli Asistent (api/voicebot_api.php) və səsli doğrulama |
voice_otp | Səsli Doğrulama Kodu (api/voice_otp.php) |
sms | SMS API (api/sms.php). Başqa heç bir əhatə SMS-ə çıxış əldə edə bilməz |
bridge | Canlı santral məlumatı (api/crm_bridge.php): canlı zənglər, nümayəndə vəziyyəti, zəng qeydləri, səs qeydi, qara siyahı, anonslar. Yalnız açarın hesabı; göndərilən tenant_id nəzərə alınmır |
* | Bütün API-lər. Yalnız həqiqətən lazımdırsa |
Prinsip: hər inteqrasiyaya ayrı açar, hər açara ən az səlahiyyət. E-ticarət saytınızın SMS açarı sızarsa hücumçu zəng başlada bilməz; yalnız həmin açarı ləğv edirsiniz.
Açarı göndərmə
Açar başlıqda göndərilir:
Authorization: Bearer bt_xxxxxxxx
Authorization başlığını ayarlaya bilməyən mühitlər üçün X-Api-Key: bt_xxxxxxxx də qəbul edilir.
URL-də açar (?key=)
?key=bt_… formatı yeni açarlarda bağlıdır və 401 query_key_disabled qaytarır. URL-lər veb server jurnallarına, proksi qeydlərinə, brauzer tarixçəsinə və Referer başlığına düşür; açar oradan sızır. Yalnız başlıq göndərə bilməyən köhnə sistem üçün, açar ayarlarında "Açarı URL-də qəbul et" açıla bilər. Panel bu açarları qırmızı ?key= nişanı ilə göstərir.
V54 əvvəl yaradılmış açarlarda köhnə inteqrasiyalar qopmasın deyə bu icazə açıq saxlanıldı. İnteqrasiyanızı başlığa köçürdükdən sonra bağlayın.
İcazəli IP
Açar ayarlarındakı İcazəli IP ünvanları sahəsinə sətir başına bir IP ya da CIDR bloku yazılır:
85.111.89.220
10.20.0.0/24
2a01:4f8:c0c:1234::/64
Siyahı boşdursa hər IP qəbul edilir. Doludursa siyahıdakı ünvanlardan kənardan gələn sorğu açar düzgün olsa belə 403 ip_not_allowed qaytarır. Yazılacaq ünvan, API-ni çağıran serverinizin çıxış IP-sidir (öz kompüterinizin deyil). Əmin deyilsinizsə, jurnalda həmin serverdən gələn sorğunun IP sütununa baxın.
Sorğu imzalama
İmza, açar ələ keçirilsə belə sorğu göndərilə bilməməsini təmin edir: hücumçunun ayrıca imza sirrinə ehtiyacı var və bu sirr heç bir sorğuda şəbəkə üzərindən getmir. İmza eyni zamanda:
- Gövdəni kilidləyir: yolda tək bir simvol dəyişərsə imza tutmur.
- Təkrar oxutmanı əngəlləyir: hər nonce yalnız bir dəfə qəbul edilir; tutulan sorğu ikinci dəfə göndərilə bilməz.
- Köhnəlmiş sorğunu rədd edir: vaxt damğası server saatından ±5 dəqiqədən çox sapsa sorğu rədd edilir.
Açarda İmzalı sorğu məcburidir açıqdırsa hər sorğu imzalı olmalıdır. Bağlı olsa belə imza başlıqlarını göndərsəniz imza yenə doğrulanır; yanlış imza səssizcə keçmir.
Başlıqlar
| Başlıq | Dəyər |
|---|---|
Authorization | Bearer bt_… |
X-Bt-Timestamp | Unix vaxtı, saniyə (məs. 1790802088) |
X-Bt-Nonce | Hər sorğuda yeni, 16-64 simvol A-Z a-z 0-9 _ - (məs. 32 hex) |
X-Bt-Signature | v1= + imzanın kiçik hərfli hex forması |
Kanonik mətn
İmzalanan mətn, aralarında \n (LF) olan altı sətirdir:
v1
POST
/api/sms.php?action=send
1790802088
9f86d081884c7d659a2feaa0c55ad015
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
| Sətir | Məzmun |
|---|---|
| 1 | Versiya, sabit v1 |
| 2 | HTTP metodu, böyük hərflə (GET, POST) |
| 3 | Yol və sorğu sətri, sorğunun göndərildiyi halı ilə (/api/sms.php?action=send). Domen adı və sxem daxil deyil |
| 4 | X-Bt-Timestamp dəyəri |
| 5 | X-Bt-Nonce dəyəri |
| 6 | Gövdənin SHA-256 xülasəsi, kiçik hərfli hex. Gövdəsiz sorğuda və multipart/form-data (fayl yükləmə) sorğularında boş mətnin özəti: e3b0c442…b855 |
İmza: hex( HMAC-SHA256( anahtar = imza_sırrı, mesaj = kanonik_metin ) )
PHP
function buluthat_request(string $method, string $url, string $token, string $secret, ?array $data = null): array
{
$body = $data === null ? '' : json_encode($data, JSON_UNESCAPED_UNICODE);
$p = parse_url($url);
$uri = ($p['path'] ?? '/') . (isset($p['query']) ? '?' . $p['query'] : '');
$ts = (string)time();
$nonce = bin2hex(random_bytes(16));
$canonical = implode("\n", ['v1', strtoupper($method), $uri, $ts, $nonce, hash('sha256', $body)]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'X-Bt-Timestamp: ' . $ts,
'X-Bt-Nonce: ' . $nonce,
'X-Bt-Signature: v1=' . hash_hmac('sha256', $canonical, $secret),
],
]);
if ($body !== '') {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body); // imzalanan gövdenin AYNISI
}
$res = json_decode((string)curl_exec($ch), true) ?: [];
curl_close($ch);
return $res;
}
$token = getenv('BULUTHAT_TOKEN'); // bt_...
$secret = getenv('BULUTHAT_SECRET'); // bts_...
print_r(buluthat_request('POST', 'https://api.buluthat.com/api/sms.php?action=send', $token, $secret, [
'header' => 'FIRMAM', 'message' => 'Siparişiniz kargoya verildi.', 'phones' => ['05321234567'],
]));
Hazır klientlər də imzalayır: buluthat-autocall-client.php və buluthat-voice-otp-client.php dördüncü parametrdə ['signing_secret' => 'bts_…'] alır.
Node.js
const crypto = require('crypto');
async function buluthatRequest(method, url, token, secret, data) {
const body = data === undefined ? '' : JSON.stringify(data);
const u = new URL(url);
const ts = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString('hex');
const canonical = ['v1', method.toUpperCase(), u.pathname + u.search, ts, nonce,
crypto.createHash('sha256').update(body).digest('hex')].join('\n');
const signature = crypto.createHmac('sha256', secret).update(canonical).digest('hex');
const res = await fetch(url, {
method,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'X-Bt-Timestamp': ts,
'X-Bt-Nonce': nonce,
'X-Bt-Signature': `v1=${signature}`,
},
body: body || undefined,
});
return res.json();
}
buluthatRequest('GET', 'https://api.buluthat.com/api/voice_otp.php?action=status&id=42',
process.env.BULUTHAT_TOKEN, process.env.BULUTHAT_SECRET).then(console.log);
Python
import hashlib, hmac, json, os, secrets, time, urllib.parse
import requests
def buluthat_request(method, url, token, secret, data=None):
body = b"" if data is None else json.dumps(data, ensure_ascii=False).encode("utf-8")
u = urllib.parse.urlsplit(url)
uri = u.path + ("?" + u.query if u.query else "")
ts = str(int(time.time()))
nonce = secrets.token_hex(16)
canonical = "\n".join(["v1", method.upper(), uri, ts, nonce, hashlib.sha256(body).hexdigest()])
sig = hmac.new(secret.encode(), canonical.encode("utf-8"), hashlib.sha256).hexdigest()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"X-Bt-Timestamp": ts,
"X-Bt-Nonce": nonce,
"X-Bt-Signature": f"v1={sig}",
}
return requests.request(method, url, data=body or None, headers=headers, timeout=30).json()
print(buluthat_request("POST", "https://api.buluthat.com/api/autocall.php?action=add_leads",
os.environ["BULUTHAT_TOKEN"], os.environ["BULUTHAT_SECRET"],
{"campaign_id": 12, "leads": [{"phone": "05321234567", "name": "Ayşe Yılmaz"}]}))
Komanda sətri (bash + openssl)
TOKEN=bt_xxx; SECRET=bts_xxx
URI='/api/autocall.php?action=ping'
TS=$(date +%s); NONCE=$(openssl rand -hex 16)
BODYHASH=$(printf '' | openssl dgst -sha256 -hex | awk '{print $NF}')
SIG=$(printf 'v1\nGET\n%s\n%s\n%s\n%s' "$URI" "$TS" "$NONCE" "$BODYHASH" \
| openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')
curl "https://api.buluthat.com$URI" -H "Authorization: Bearer $TOKEN" \
-H "X-Bt-Timestamp: $TS" -H "X-Bt-Nonce: $NONCE" -H "X-Bt-Signature: v1=$SIG"
İmza niyə tutmur?
| Əlamət | Səbəb |
|---|---|
signature_invalid yalnız POST-da | İmzaladığınız gövdə ilə göndərdiyiniz gövdə fərqlidir. JSON-u bir dəfə yaradın, eyni dəyişəni həm xülasəyə həm sorğuya verin |
signature_invalid Türk hərfli sorğuda | Yolu özünüz birləşdirib fərqli kodlaşdırdınız. İmzada, klientin həqiqətən göndərdiyi URL-dəki yolu və sorğunu istifadə edin (parse_url / new URL()) |
signature_expired | Serverinizin saatı sürüşüb. NTP (timedatectl set-ntp true) açın; tolerans ±300 san |
signature_replayed | Eyni nonce ikinci dəfə göndərildi. Təkrar cəhddə (retry) nonce və vaxt damğasını yenidən yaradıb yenidən imzalayın |
signature_required | Açarda imza məcburidir, lakin başlıqlar çatışmır |
signature_not_configured | Açarın imza sirri yoxdur; paneldən "Yeni imza sirri" yaradın |
Xəta kodları
Kimlik və təhlükəsizlik xətaları bütün JSON nöqtələrində eynidir code dəyərləri ilə qayıdır. Zəng idarəetməsi və növbə nöqtələri (Verimor uyğun) eyni HTTP kodunu düz mətn mesajla qaytarır.
| HTTP | code | Nə etməli |
|---|---|---|
| 401 | missing_token | Authorization: Bearer … başlığını əlavə edin |
| 401 | invalid_token | Açar yanlışdır, ləğv edilib ya da heç yoxdur. Təkrar cəhd etməyin, ayarı düzəldin |
| 401 | token_expired | Paneldən açarı yeniləyin |
| 401 | query_key_disabled | Açarı URL əvəzinə başlıqda göndərin |
| 401 | signature_* | Yuxarıdakı cədvələ baxın |
| 403 | ip_not_allowed | Serverinizin çıxış IP-sini açarın siyahısına əlavə edin |
| 403 | scope_denied | Açara müvafiq səlahiyyəti verin ya da düzgün açarı istifadə edin |
| 403 | account_inactive | Hesab bağlıdır; dəstəklə əlaqə saxlayın |
| 403 | module_disabled | Əlaqəli xidmət paketinizdə açıq deyil |
| 413 | payload_too_large | Sorğunu hissələrə bölün (məs. add_leads ən çox 5.000 qeyd) |
| 429 | rate_limited / tenant_rate_limited | Retry-After qədər gözləyin |
| 429 | ip_locked | Bu IP-dən çoxlu sayda yanlış cəhd gəldi; yanlış konfiqurasiyanı düzəldin, kilid öz-özünə açılır |
Xəta cavabı nümunəsi:
{ "ok": false, "error": "Bu anahtarın bu API için yetkisi yok.", "code": "scope_denied", "request_id": "df5fca3a599653f6" }
Sürət limitləri
| Limit | Defolt |
|---|---|
| Açar başına | dəqiqədə 120 sorğu (açar ayarından azaldıla bilər) |
| Hesabın bütün açarlarının cəmi | dəqiqədə 600 sorğu |
Nümayəndə vəziyyətləri (agent_statuses) | əlavə olaraq hesab başına dəqiqədə 2 sorğu |
Hər uğurlu cavabda qalan hüquq başlıqlarda gəlir:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1790802120
429 aldıqda Retry-After (saniyə) qədər gözləyin. Şəbəkə xətası və 5xx üçün eksponensial geri çəkilmə istifadə edin (1 san, 2 san, 4 san… ən çox 5 cəhd). 401/403 xətalarını təkrar cəhd etməyin: konfiqurasiya xətasıdır, cəhdlər kobud qüvvə kilidini işə salır.
Açar yeniləmə
Açarları 90-180 gündən bir, işçi ayrılanda ya da sızma şübhəsində yeniləyin. Fasiləsiz keçid:
- API Açarları > müvafiq açar > Ayarlar > Açarı yenilə. "Köhnəsi 24 saat işləsin" seçin.
- Eyni ayarlarla yeni açar və yeni imza sirri yaradılır, ekranda bir dəfə göstərilir.
- İnteqrasiyanızı yeni dəyərlərlə yeniləyin.
- Jurnalda köhnə açarın prefiksi (
bt_7820d84…) artıq görünmürsə gözləyin; müddət bitəndə köhnə açar öz-özünə bağlanır.
Sızma şübhəsində "Köhnəsini dərhal bağla" seçin; köhnə açarla gələn sorğular dərhal rədd edilir.
Sorğu jurnalı
API Açarları səhifəsinin altındakı Sorğu jurnalı hər sorğunu göstərir: vaxt, açar prefiksi, IP, nöqtə və əməliyyat, HTTP vəziyyəti, xəta kodu, müddət, imzalıdır ya yox. Son 24 saatın xülasəsi (sorğu, xəta, sürət limiti, kimlik xətası, imzalı sorğu, fərqli IP sayı) səhifənin üstündədir. Qeydlər 90 gün saxlanılır.
Hər cavabda X-Request-Id başlığı var. Dəstək sorğusunda bu dəyəri yazın; sorğunuzu jurnalda dərhal tapırıq. Açarı ya da imza sirrini dəstək sorğusuna, e-poçta ya da ekran görüntüsünə heç vaxt qoymayın.
Tanımadığınız bir IP ya da gözləmədiyiniz saatlarda sorğu görsəniz açarı dərhal "Köhnəsini dərhal bağla" ilə yeniləyin.
Byfix CRM quraşdırması
Byfix CRM Buluthat-a iki ayrı kimliklə qoşulur:
| Ayar (CRM) | Dəyər |
|---|---|
| Ayarlar > Avtomatik Zəng > API Açarı | Buluthat-da yaradılan bt_… açarı (əhatə: autocall + voicebot + voice_otp + call) |
| Ayarlar > Avtomatik Zəng > İmza Sirri | Eyni açarın bts_… sirri. Doludursa CRM-in Buluthat-a göndərdiyi hər sorğu imzalanır |
| VoIP ayarları > Buluthat API Token | Canlı ekran / CDR körpüsü (crm_bridge); Buluthat komandası verir, yuxarıdakı açardan fərqlidir |
Tövsiyə olunan sıra: açarı İmzalı sorğu məcburidir bağlı yaradın, CRM-ə açarı və sirri daxil edin, jurnalda sorğuların imzalı nişanı ilə gəldiyini görün, sonra açarda imzanı məcburi edin. Açara CRM serverinin IP-sini də əlavə edin.
Kliklə-zəng (begin_call) artıq açarı URL-də deyil, başlıqda göndərir. CRM yeniləməsindən sonra köhnə açardakı "URL-də açar" icazəsini bağlaya bilərsiniz.
ByCRM quraşdırması
ByCRM-də hər firma Buluthat-a öz açarı ilə bağlanır; firmalar bir-birinin məlumatını görə bilməz.
- Buluthat panelində firmanın hesabı ilə API Açarları > Yeni açar: səlahiyyətlər Canlı Santral Məlumatı, Zəng İdarəetməsi və Növbələr, Avtomatik Zəng (istifadə olunursa Səsli Asistent, Səsli Doğrulama Kodu). ByCRM serverinin IP-sini yazın.
- ByCRM > İnteqrasiyalar > Buluthat:
| Sahə | Dəyər |
|---|---|
| Buluthat API Token | bt_… |
| İmza sirri | bts_… |
| Bridge Token | eyni bt_… açarı |
| CRM Bridge URL | https://api.buluthat.com/api/crm_bridge.php |
| Autocall URL | https://api.buluthat.com/api/autocall.php |
| Buluthat Tenant ID / PBX Server ID | İstifadə olunmur; açar hansı hesaba aiddirsə həmin hesabın məlumatı gəlir |
- Canlı ekranı və kliklə-zəngi sınayın, jurnalda
imzalınişanlarını görün, sonra açarda İmzalı sorğu məcburidir-nu açın.
Canlı ekran körpünü bir neçə saniyədən bir yoxladığı üçün körpü sorğularının ayrı və daha geniş sürət limiti var (açar başına dəqiqədə 1.200); jurnala yalnız yanlış körpü sorğuları yazılır.
Webhook-ların doğrulanması
Buluthat-ın sizə göndərdiyi webhook-lar da imzalıdır (X-Buluthat-Signature: sha256=…). Serverinizdə imzanı xam gövdə üzərindən doğrulamadan heç bir webhook-u emal etməyin; X-Buluthat-Delivery ilə eyni çatdırılmanı iki dəfə emal etməyin. Ətraflı: Webhook-lar.
Təhlükəsizlik yoxlama siyahısı
- [ ] Hər inteqrasiyanın öz açarı var, yalnız lazım olan əhatələrlə
- [ ] Açarlar kodda deyil, mühit dəyişənində ya da gizli idarəetmədədir (
.envGit-ə girmir) - [ ] Açar yalnız server tərəfindədir; brauzer JavaScript-ində, mobil tətbiqdə, Excel makrosunda yoxdur
- [ ] İcazəli IP siyahısı doludur
- [ ] İmzalı sorğu məcburidir açıqdır
- [ ] URL-də açar (
?key=) bağlıdır - [ ] Son istifadə tarixi var ya da təqvimdə yeniləmə xatırlatması qurulub
- [ ] Klient TLS sertifikatını doğrulayır (
CURLOPT_SSL_VERIFYPEERaçıq); doğrulama bağlı olanda araya girən biri sorğunu oxuyub dəyişə bilər - [ ] Server saatı NTP ilə sinxrondur
- [ ] Webhook imzası doğrulanır
- [ ] Jurnal ayda bir nəzərdən keçirilir; ayrılan işçinin çıxış əldə etdiyi açarlar yeniləndi
Təhlükəsizlik boşluğu bildirişi
Buluthat API-sində təhlükəsizlik boşluğu tapdığınızı düşünürsünüzsə panel daxilindəki Dəstək Mərkəzindən "Təhlükəsizlik bildirişi" mövzulu qeyd açın. Boşluğu necə təkrar yaratdığınızı və varsa X-Request-Id dəyərlərini əlavə edin. Bildirişi nəzərdən keçirib düzəldənə qədər təfərrüatları paylaşmamağınızı xahiş edirik.
