Bezpieczeństwo API
API Buluthat zarządza Twoją centralą telefoniczną: inicjuje połączenia, kończy rozmowy, wysyła SMS-y, przetwarza numery klientów. Dlatego wyciek klucza nie oznacza „widoczny raport”, lecz „połączenia wykonywane z Twojego konta”. Ta strona wyjaśnia, jak chronić klucz i jakie zabezpieczenia Buluthat stosuje w Twoim imieniu.
Adres bazowy: https://api.buluthat.com/api/
Warstwy w skrócie
Każde żądanie przechodzi po kolei przez te bramki. Jeśli któraś odrzuci, żądanie nie jest przetwarzane i trafia do dziennika.
| Kolejność | Bramka | Co robi | Błąd |
|---|---|---|---|
| 1 | Blokada brute force | Jeśli z jednego IP w ciągu 10 minut 20 razy zostanie użyty błędny klucz lub podpis, adres IP zostaje tymczasowo zablokowany | 429 ip_locked |
| 2 | Limit treści | Treść żądania większa niż 5 MB nie jest odczytywana | 413 payload_too_large |
| 3 | Klucz | Klucz jest akceptowany wyłącznie w nagłówku; w systemie przechowywany jest tylko skrót SHA-256 | 401 invalid_token |
| 4 | Czas i anulowanie | Klucz, który wygasł lub został unieważniony, jest odrzucany | 401 token_expired |
| 5 | Dozwolone IP | Jeśli dla klucza zdefiniowano listę IP, przechodzą tylko żądania z tych adresów | 403 ip_not_allowed |
| 6 | Status konta | Klucze zamkniętego lub nieaktywnego konta nie działają | 403 account_inactive |
| 7 | Zakres | Klucz ma dostęp wyłącznie do dozwolonych API | 403 scope_denied |
| 8 | Podpis | Jeśli w kluczu włączono „wymagaj podpisanego żądania”, weryfikowany jest podpis HMAC, znacznik czasu i jednorazowy nonce | 401 signature_* |
| 9 | Limit zapytań | Limit na minutę na klucz i łącznie na konto | 429 rate_limited |
Szybki start
- W panelu Konto i wsparcie > Klucze API otwórz stronę (widzi ją tylko osoba upoważniona konta).
- Nowy klucz: nadaj nazwę, zaznacz tylko wymagane uprawnienia, wpisz adres IP wychodzący Twojego serwera, Podpisane żądanie wymaganeOtwórz
- Skopiuj dwie wartości wyświetlone na ekranie jednorazowo: klucz API (
bt_…, w każdym żądaniuAuthorizationidzie w nagłówku) oraz sekret podpisu (bts_…, służy do podpisywania żądania, nigdy nie jest wysyłane). - Wypróbuj połączenie:
curl "https://api.buluthat.com/api/autocall.php?action=ping" \
-H "Authorization: Bearer bt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Jeśli podpis jest obowiązkowy, to żądanie 401 signature_required zwraca; poniższe Podpisywanie żądań użyj jednego z przykładów w sekcji.
Klucz i sekret podpisu nie są w systemie przechowywane w postaci możliwej do odczytania nie jest przechowywane. W razie utraty nie odzyskamy go; klucz odnawiasz w panelu.
Zakresy
Każdy klucz jest tworzony z jednym lub kilkoma zakresami. Żądanie do API spoza zakresu 403 scope_denied zwraca.
| Zakres | API, które otwiera |
|---|---|
autocall | Automatyczne połączenia (api/autocall.php). Dla zgodności ze starszymi integracjami otwiera także punkty końcowe asystenta głosowego, głosowej weryfikacji i sterowania połączeniami |
call | Sterowanie połączeniami i kolejki: begin_call, bridge, hangup, transfer, mute, queues, queues_pending, queue_user_list, queue_manage_users, agent_statuses |
voicebot | Asystent Głosowy (api/voicebot_api.php) i głosową weryfikację |
voice_otp | Głosowy kod weryfikacyjny (api/voice_otp.php) |
sms | API SMS (api/sms.php). Żaden inny zakres nie ma dostępu do SMS |
bridge | Dane centrali na żywo (api/crm_bridge.php): połączenia na żywo, status konsultantów, nagrania połączeń, nagrania rozmów, czarna lista, komunikaty. Wyłącznie konto klucza; wysłane tenant_id jest ignorowane |
* | Wszystkie API. Tylko jeśli naprawdę potrzebne |
Zasada: oddzielny klucz dla każdej integracji, minimum uprawnień dla każdego klucza. Jeśli klucz SMS Twojego sklepu internetowego wycieknie, napastnik nie może inicjować połączeń; unieważniasz tylko ten klucz.
Nie wysyłaj klucza
Klucz w nagłówku jest wysyłane:
Authorization: Bearer bt_xxxxxxxx
Authorization dla środowisk, które nie mogą ustawić nagłówka X-Api-Key: bt_xxxxxxxx także jest akceptowane.
Klucz w adresie URL (?key=)
?key=bt_… format w nowych kluczach jest wyłączone i 401 query_key_disabled zwraca. Adresy URL trafiają do logów serwera WWW, rejestrów proxy, historii przeglądarki i Referer trafia do nagłówka; klucz stamtąd wycieka. Tylko dla starszego systemu, który nie może wysłać nagłówka, w ustawieniach klucza „Akceptuj klucz w adresie URL” można otworzyć. Panel oznacza te klucze na czerwono ?key= pokazuje z odznaką.
W kluczach wygenerowanych przed V54 to uprawnienie zostało pozostawione włączone, aby nie zerwać starszych integracji. Wyłącz je po przeniesieniu integracji do nagłówka.
Dozwolone IP
W ustawieniach klucza Dozwolone adresy IP w polu wpisuje się po jednym IP lub bloku CIDR w wierszu:
85.111.89.220
10.20.0.0/24
2a01:4f8:c0c:1234::/64
Jeśli lista jest pusta, akceptowany jest każdy adres IP. Jeśli jest wypełniona, żądanie spoza adresów z listy jest odrzucane, nawet gdy klucz jest poprawny 403 ip_not_allowed zwraca. Adres do wpisania to ten, który wywołuje API to adres IP wychodzący Twojego serwera (a nie Twojego komputera). Jeśli nie masz pewności, sprawdź w dzienniku kolumnę IP żądania pochodzącego z tego serwera.
Podpisywanie żądań
Podpis sprawia, że nawet po przejęciu klucza nie można wysyłać żądań: napastnik potrzebuje dodatkowo sekretu podpisu, który nie jest przesyłany siecią w żadnym żądaniu. Podpis jednocześnie:
- Blokuje treść: jeśli zmieni się choć jeden znak w ścieżce, podpis się nie zgodzi.
- Zapobiega ponownemu odtworzeniu: każdy nonce jest akceptowany tylko raz; przechwycone żądanie nie może zostać wysłane ponownie.
- Odrzuca przeterminowane żądanie: żądanie jest odrzucane, jeśli znacznik czasu odbiega od czasu serwera o więcej niż ±5 minut.
W kluczu Podpisane żądanie wymagane gdy włączone, każde żądanie musi być podpisane. Nawet gdy wyłączone, jeśli wyślesz nagłówki podpisu, podpis i tak jest weryfikowany; błędny podpis nie przejdzie bezszelestnie.
Nagłówki
| Nagłówek | Wartość |
|---|---|
Authorization | Bearer bt_… |
X-Bt-Timestamp | Czas Unix, w sekundach (np. 1790802088) |
X-Bt-Nonce | Przy każdym żądaniu nowy, 16-64 znaków A-Z a-z 0-9 _ - (np. 32 hex) |
X-Bt-Signature | v1= + podpis zapisany małymi literami hex |
Tekst kanoniczny
Podpisywany tekst, między nimi \n (LF) to sześć wierszy:
v1
POST
/api/sms.php?action=send
1790802088
9f86d081884c7d659a2feaa0c55ad015
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
| Wiersz | Treść |
|---|---|
| 1 | Wersja, stała v1 |
| 2 | Metoda HTTP, wielkimi literami (GET, POST) |
| 3 | Ścieżka i ciąg zapytania, w postaci, w jakiej żądanie zostało wysłane (/api/sms.php?action=send). Nazwa domeny i schemat nie są wliczone |
| 4 | X-Bt-Timestamp wartość |
| 5 | X-Bt-Nonce wartość |
| 6 | Skrót SHA-256 treści, małymi literami hex. Przy żądaniu bez treści i multipart/form-data (przesyłanie plików) — skrót pustego tekstu: e3b0c442…b855 |
Podpis: 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'],
]));
Gotowi klienci również podpisują: buluthat-autocall-client.php i buluthat-voice-otp-client.php w czwartym parametrze ['signing_secret' => 'bts_…'] otrzymuje.
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"}]}))
Wiersz poleceń (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"
Dlaczego podpis się nie zgadza?
| Objaw | Przyczyna |
|---|---|
signature_invalid tylko w POST | Treść, którą podpisałeś, różni się od treści, którą wysłałeś. JSON jednorazowo wygeneruj, tę samą zmienną podaj zarówno w podsumowaniu, jak i w żądaniu |
signature_invalid W zapytaniu z tureckimi znakami | Złożyłeś ścieżkę samodzielnie i zakodowałeś inaczej. W podpisie użyj ścieżki i zapytania z adresu URL, który klient faktycznie wysyła (parse_url / new URL()) |
signature_expired | Zegar Twojego serwera się rozjechał. NTP (timedatectl set-ntp true) otwórz; tolerancja ±300 s |
signature_replayed | Wysłano ten sam nonce drugi raz. Przy ponowieniu (retry) zmień nonce i znacznik czasu wygeneruj ponownie i podpisz ponownie |
signature_required | Klucz wymaga podpisu, ale brakuje nagłówków |
signature_not_configured | Klucz nie ma sekretu podpisu; wygeneruj „Nowy sekret podpisu” w panelu |
Kody błędów
Błędy uwierzytelniania i bezpieczeństwa są takie same we wszystkich punktach końcowych JSON code zwraca wartości. Punkty końcowe sterowania połączeniami i kolejek (zgodne z Verimor) zwracają ten sam kod HTTP z komunikatem w czystym tekście.
| HTTP | code | Co robić |
|---|---|---|
| 401 | missing_token | Authorization: Bearer … dodaj nagłówek |
| 401 | invalid_token | Klucz jest błędny, unieważniony lub w ogóle go brak. Nie ponawiaj, popraw ustawienie |
| 401 | token_expired | Odnów klucz w panelu |
| 401 | query_key_disabled | Wysyłaj klucz w nagłówku zamiast w adresie URL |
| 401 | signature_* | Zobacz powyższą tabelę |
| 403 | ip_not_allowed | Dodaj adres IP wychodzący swojego serwera do listy klucza |
| 403 | scope_denied | Nadaj kluczowi odpowiednie uprawnienie lub użyj właściwego klucza |
| 403 | account_inactive | Konto zamknięte; skontaktuj się ze wsparciem |
| 403 | module_disabled | Dana usługa nie jest włączona w Twoim pakiecie |
| 413 | payload_too_large | Podziel żądanie na części (np. add_leads maksymalnie 5.000 rekordów) |
| 429 | rate_limited / tenant_rate_limited | Retry-After poczekaj do |
| 429 | ip_locked | Z tego adresu IP nadeszło wiele błędnych prób; popraw błędną konfigurację, blokada zniknie samoczynnie |
Przykład odpowiedzi z błędem:
{ "ok": false, "error": "Bu anahtarın bu API için yetkisi yok.", "code": "scope_denied", "request_id": "df5fca3a599653f6" }
Limity zapytań
| Limit | Domyślnie |
|---|---|
| Na klucz | 120 żądań na minutę (można obniżyć w ustawieniach klucza) |
| Łącznie wszystkich kluczy konta | 600 żądań na minutę |
Statusy konsultantów (agent_statuses) | dodatkowo 2 żądań na minutę na konto |
W każdej udanej odpowiedzi pozostałe limity przychodzą w nagłówkach:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1790802120
429 po otrzymaniu Retry-After Odczekaj (sekundy). Błąd sieci i 5xx użyj wykładniczego wycofywania (1 s, 2 s, 4 s… maksymalnie 5 prób). 401/403 błędy nie ponawiaj: to błąd konfiguracji, próby uruchamiają blokadę brute force.
Odnawianie klucza
Odnawiaj klucze co 90-180 dni, po odejściu pracownika lub w razie podejrzenia wycieku. Płynne przełączenie:
- Klucze API > odpowiedni klucz > Ustawienia > Odnów klucz. Wybierz „Stary działa przez 24 godz.”.
- Z tymi samymi ustawieniami generowany jest nowy klucz i nowy sekret podpisu, pokazywane jeden raz na ekranie.
- Zaktualizuj swoją integrację o nowe wartości.
- W dzienniku prefiks starego klucza (
bt_7820d84…) nie jest już widoczny, poczekaj; po upływie czasu stary klucz wygaśnie samoczynnie.
W razie podejrzenia włamania Wybierz „Zamknij stary natychmiast”; żądania ze starym kluczem zostaną od razu odrzucone.
Dziennik żądań
pod stroną Klucze API Dziennik żądań pokazuje każde żądanie: czas, prefiks klucza, IP, punkt końcowy i akcję, status HTTP, kod błędu, czas trwania, czy podpisane. Podsumowanie ostatnich 24 godzin (żądania, błędy, limit zapytań, błędy uwierzytelniania, żądania podpisane, liczba różnych IP) znajduje się u góry strony. Rekordy są przechowywane 90 dni.
W każdej odpowiedzi X-Request-Id jest nagłówek. Podaj tę wartość w zgłoszeniu do wsparcia; od razu znajdziemy Twoje żądanie w dzienniku. Nigdy nie umieszczaj klucza ani sekretu podpisu w zgłoszeniu do pomocy, e-mailu ani zrzucie ekranu.
Jeśli zobaczysz żądanie z nieznanego IP lub w nieoczekiwanych godzinach, natychmiast odnów klucz opcją „Zamknij stary natychmiast”.
Konfiguracja Byfix CRM
Byfix CRM łączy się z Buluthat za pomocą dwóch oddzielnych tożsamości:
| Ustawienie (CRM) | Wartość |
|---|---|
| Ustawienia > Automatyczne połączenia > Klucz API | Wygenerowane w Buluthat bt_… klucz (zakres: autocall + voicebot + voice_otp + call) |
| Ustawienia > Automatyczne połączenia > Sekret podpisu | Ten sam klucz bts_… sekret. Jeśli wypełniony, każde żądanie wysyłane przez CRM do Buluthat jest podpisywane |
| Ustawienia VoIP > Buluthat API Token | Ekran na żywo / most CDR (crm_bridge); wydaje zespół Buluthat, jest odrębny od powyższego klucza |
Zalecana kolejność: klucz Podpisane żądanie wymagane wygeneruj z wyłączonym, wpisz klucz i sekret w CRM, w dzienniku żądania imzalı zobacz, że przychodzi z odznaką, następnie włącz w kluczu obowiązkowy podpis. Dodaj do klucza także adres IP serwera CRM.
Click-to-call (begin_call) przesyła teraz klucz w nagłówku, a nie w adresie URL. Po aktualizacji CRM możesz wyłączyć w starym kluczu uprawnienie „Klucz w adresie URL”.
Konfiguracja ByCRM
W ByCRM każda firma do Buluthat własnym kluczem jest powiązany; firmy nie widzą nawzajem swoich danych.
- W panelu Buluthat z kontem firmy Klucze API > Nowy klucz: uprawnienia Dane centrali na żywo, Sterowanie połączeniami i kolejki, Automatyczne połączenia (jeśli jest używany Asystent Głosowy, Głosowy kod weryfikacyjny). Podaj adres IP serwera ByCRM.
- ByCRM > Integracje > Buluthat:
| Pole | Wartość |
|---|---|
| Buluthat API Token | bt_… |
| sekret podpisu | bts_… |
| Bridge Token | to samo bt_… klucz |
| 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 | Nie jest używany; zwracane są dane konta, do którego należy klucz |
- Wypróbuj ekran na żywo i click-to-call, w dzienniku
imzalızobacz odznaki, następnie w kluczu Podpisane żądanie wymaganeOtwórz
Ponieważ ekran na żywo odpytuje most co kilka sekund, żądania mostu mają osobny, szerszy limit (na klucz 1.200 na minutę); do dziennika zapisywane są tylko błędne żądania mostu.
Weryfikacja webhooków
Webhooki wysyłane do Ciebie przez Buluthat są również podpisane (X-Buluthat-Signature: sha256=…). Na serwerze podpis surowa treść nie przetwarzaj żadnego webhooka bez weryfikacji przez; X-Buluthat-Delivery nie przetwarzaj dwa razy tego samego dostarczenia. Szczegóły: Webhooki.
Lista kontrolna bezpieczeństwa
- [ ] Każda integracja ma własny klucz, tylko z wymaganymi zakresami
- [ ] Klucze nie w kodzie, lecz w zmiennej środowiskowej lub zarządzaniu sekretami (
.envNie trafia do Git) - [ ] Klucz tylko po stronie serwera; nie ma go w JavaScript przeglądarki, aplikacji mobilnej, makrze Excela
- [ ] Lista dozwolonych IP jest wypełniona
- [ ] Wymagane podpisane żądanie jest włączone
- [ ] Klucz w adresie URL (
?key=) wyłączone - [ ] Jest data wygaśnięcia lub w kalendarzu ustawiono przypomnienie o odnowieniu
- [ ] Klient weryfikuje certyfikat TLS (
CURLOPT_SSL_VERIFYPEERwłączone); gdy weryfikacja jest wyłączona, ktoś pośredniczący może odczytać i zmienić żądanie - [ ] Czas serwera zsynchronizowany przez NTP
- [ ] Podpis webhooka jest weryfikowany
- [ ] Dziennik jest przeglądany raz w miesiącu; klucze, do których miał dostęp odchodzący pracownik, zostały odnowione
Zgłoszenie luki w zabezpieczeniach
Jeśli sądzisz, że znalazłeś lukę w zabezpieczeniach API Buluthat, zgłoś to w Centrum Pomocy w panelu „Powiadomienie bezpieczeństwa” załóż zgłoszenie z tematem. Opisz, jak odtworzyć lukę, a jeśli jest X-Request-Id dodaj wartości. Prosimy nie udostępniać szczegółów, dopóki nie sprawdzimy i nie poprawimy zgłoszenia.
