API Güvenliği
Buluthat API'si telefon santralinizi yönetir: arama başlatır, çağrı kapatır, SMS gönderir, müşteri numaralarını işler. Bu yüzden anahtarın sızması "bir rapor görünür" değil, "hesabınızdan arama yapılır" demektir. Bu sayfa anahtarınızı nasıl koruyacağınızı ve Buluthat'ın sizin yerinize hangi korumaları uyguladığını anlatır.
Temel adres: https://api.buluthat.com/api/
Katmanlar bir bakışta
Her istek sırayla şu kapılardan geçer. Biri reddederse istek işlenmez ve günlüğe yazılır.
| Sıra | Kapı | Ne yapar | Hata |
|---|---|---|---|
| 1 | Kaba kuvvet kilidi | Bir IP 10 dakikada 20 kez hatalı anahtar ya da imza denerse o IP geçici olarak kilitlenir | 429 ip_locked |
| 2 | Gövde sınırı | 5 MB'tan büyük istek gövdesi okunmaz | 413 payload_too_large |
| 3 | Anahtar | Anahtar yalnızca başlıkta kabul edilir; sistemde yalnızca SHA-256 özeti tutulur | 401 invalid_token |
| 4 | Süre ve iptal | Süresi dolmuş ya da iptal edilmiş anahtar reddedilir | 401 token_expired |
| 5 | İzinli IP | Anahtara IP listesi tanımlıysa yalnızca o adreslerden gelen istek geçer | 403 ip_not_allowed |
| 6 | Hesap durumu | Kapatılmış ya da pasif hesabın anahtarları çalışmaz | 403 account_inactive |
| 7 | Kapsam | Anahtar yalnızca izin verilen API'lere girer | 403 scope_denied |
| 8 | İmza | Anahtarda "imzalı istek zorunlu" açıksa HMAC imzası, zaman damgası ve tek kullanımlık nonce doğrulanır | 401 signature_* |
| 9 | Hız sınırı | Anahtar başına ve hesap toplamında dakikalık sınır | 429 rate_limited |
Hızlı başlangıç
- Panelde Hesap ve Destek > API Anahtarları sayfasını açın (yalnızca hesap yetkilisi görür).
- Yeni anahtar: ad verin, yalnızca gereken yetkileri işaretleyin, sunucunuzun çıkış IP'sini yazın, İmzalı istek zorunlu'yu açın.
- Ekranda bir kez gösterilen iki değeri kopyalayın: API anahtarı (
bt_…, her istekteAuthorizationbaşlığında gider) ve imza sırrı (bts_…, isteği imzalamak için kullanılır, hiçbir zaman gönderilmez). - Bağlantıyı deneyin:
curl "https://api.buluthat.com/api/autocall.php?action=ping" \
-H "Authorization: Bearer bt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
İmza zorunluysa bu istek 401 signature_required döner; aşağıdaki İstek imzalama bölümündeki örneklerden birini kullanın.
Anahtar ve imza sırrı sistemde geri okunabilir biçimde saklanmaz. Kaybederseniz kurtaramayız; panelden anahtarı yenilersiniz.
Kapsamlar
Her anahtar bir ya da birkaç kapsamla üretilir. Kapsam dışı bir API'ye gelen istek 403 scope_denied döner.
| Kapsam | Açtığı API'ler |
|---|---|
autocall | Otomatik Arama (api/autocall.php). Eski entegrasyonlarla uyum için sesli asistan, sesli doğrulama ve çağrı kontrolü uçlarını da açar |
call | Çağrı kontrolü ve kuyruklar: begin_call, bridge, hangup, transfer, mute, queues, queues_pending, queue_user_list, queue_manage_users, agent_statuses |
voicebot | Sesli Asistan (api/voicebot_api.php) ve sesli doğrulama |
voice_otp | Sesli Doğrulama Kodu (api/voice_otp.php) |
sms | SMS API (api/sms.php). Başka hiçbir kapsam SMS'e erişemez |
bridge | Canlı santral verisi (api/crm_bridge.php): canlı çağrılar, temsilci durumu, çağrı kayıtları, ses kaydı, kara liste, anonslar. Yalnızca anahtarın hesabı; gönderilen tenant_id yok sayılır |
* | Tüm API'ler. Yalnızca gerçekten gerekiyorsa |
İlke: her entegrasyona ayrı anahtar, her anahtara en az yetki. E-ticaret sitenizin SMS anahtarı sızarsa saldırgan arama başlatamaz; yalnızca o anahtarı iptal edersiniz.
Anahtarı gönderme
Anahtar başlıkta gönderilir:
Authorization: Bearer bt_xxxxxxxx
Authorization başlığını ayarlayamayan ortamlar için X-Api-Key: bt_xxxxxxxx de kabul edilir.
URL'de anahtar (?key=)
?key=bt_… biçimi yeni anahtarlarda kapalıdır ve 401 query_key_disabled döner. URL'ler web sunucusu loglarına, proxy kayıtlarına, tarayıcı geçmişine ve Referer başlığına düşer; anahtar oradan sızar. Yalnızca başlık gönderemeyen eski bir sistem için, anahtar ayarlarında "Anahtarı URL'de kabul et" açılabilir. Panel bu anahtarları kırmızı ?key= rozetiyle gösterir.
V54 öncesinde üretilmiş anahtarlarda eski entegrasyonlar kopmasın diye bu izin açık bırakıldı. Entegrasyonunuzu başlığa taşıdıktan sonra kapatın.
İzinli IP
Anahtar ayarlarındaki İzinli IP adresleri alanına satır başına bir IP ya da CIDR bloğu yazılır:
85.111.89.220
10.20.0.0/24
2a01:4f8:c0c:1234::/64
Liste boşsa her IP kabul edilir. Doluysa listedeki adresler dışından gelen istek anahtar doğru olsa bile 403 ip_not_allowed döner. Yazılacak adres, API'yi çağıran sunucunuzun çıkış IP'sidir (kendi bilgisayarınızın değil). Emin değilseniz günlükte o sunucudan gelen isteğin IP sütununa bakın.
İstek imzalama
İmza, anahtar ele geçirilse bile istek atılamamasını sağlar: saldırganın ayrıca imza sırrına ihtiyacı vardır ve bu sır hiçbir istekte ağ üzerinden gitmez. İmza aynı zamanda:
- Gövdeyi kilitler: yolda tek bir karakter değişirse imza tutmaz.
- Tekrar oynatmayı engeller: her nonce yalnızca bir kez kabul edilir; yakalanan bir istek ikinci kez gönderilemez.
- Eskimiş isteği reddeder: zaman damgası sunucu saatinden ±5 dakikadan fazla saparsa istek reddedilir.
Anahtarda İmzalı istek zorunlu açıksa her istek imzalı olmalıdır. Kapalı olsa bile imza başlıkları gönderirseniz imza yine doğrulanır; yanlış imza sessizce geçmez.
Başlıklar
| Başlık | Değer |
|---|---|
Authorization | Bearer bt_… |
X-Bt-Timestamp | Unix zamanı, saniye (ör. 1790802088) |
X-Bt-Nonce | Her istekte yeni, 16-64 karakter A-Z a-z 0-9 _ - (ör. 32 hex) |
X-Bt-Signature | v1= + imzanın küçük harf hex hâli |
Kanonik metin
İmzalanan metin, aralarında \n (LF) olan altı satırdır:
v1
POST
/api/sms.php?action=send
1790802088
9f86d081884c7d659a2feaa0c55ad015
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
| Satır | İçerik |
|---|---|
| 1 | Sürüm, sabit v1 |
| 2 | HTTP yöntemi, büyük harf (GET, POST) |
| 3 | Yol ve sorgu dizesi, isteğin gönderildiği hâliyle (/api/sms.php?action=send). Alan adı ve şema dahil değil |
| 4 | X-Bt-Timestamp değeri |
| 5 | X-Bt-Nonce değeri |
| 6 | Gövdenin SHA-256 özeti, küçük harf hex. Gövdesiz istekte ve multipart/form-data (dosya yükleme) isteklerinde boş metnin özeti: 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 istemciler de imzalar: buluthat-autocall-client.php ve buluthat-voice-otp-client.php dördüncü parametrede ['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"}]}))
Komut satırı (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 neden tutmuyor?
| Belirti | Sebep |
|---|---|
signature_invalid yalnızca POST'ta | İmzaladığınız gövde ile gönderdiğiniz gövde farklı. JSON'u bir kez üretin, aynı değişkeni hem özete hem isteğe verin |
signature_invalid Türkçe karakterli sorguda | Yolu kendiniz birleştirip farklı kodladınız. İmzada, istemcinin gerçekten gönderdiği URL'deki yolu ve sorguyu kullanın (parse_url / new URL()) |
signature_expired | Sunucunuzun saati kaymış. NTP (timedatectl set-ntp true) açın; tolerans ±300 sn |
signature_replayed | Aynı nonce ikinci kez gitti. Yeniden denemede (retry) nonce ve zaman damgasını yeniden üretip yeniden imzalayın |
signature_required | Anahtarda imza zorunlu ama başlıklar eksik |
signature_not_configured | Anahtarın imza sırrı yok; panelden "Yeni imza sırrı" üretin |
Hata kodları
Kimlik ve güvenlik hataları tüm JSON uçlarında aynı code değerleriyle döner. Çağrı kontrolü ve kuyruk uçları (Verimor uyumlu) aynı HTTP kodunu düz metin mesajla döner.
| HTTP | code | Ne yapmalı |
|---|---|---|
| 401 | missing_token | Authorization: Bearer … başlığını ekleyin |
| 401 | invalid_token | Anahtar yanlış, iptal edilmiş ya da hiç yok. Tekrar denemeyin, ayarı düzeltin |
| 401 | token_expired | Panelden anahtarı yenileyin |
| 401 | query_key_disabled | Anahtarı URL yerine başlıkta gönderin |
| 401 | signature_* | Yukarıdaki tabloya bakın |
| 403 | ip_not_allowed | Sunucunuzun çıkış IP'sini anahtarın listesine ekleyin |
| 403 | scope_denied | Anahtara ilgili yetkiyi verin ya da doğru anahtarı kullanın |
| 403 | account_inactive | Hesap kapalı; destekle görüşün |
| 403 | module_disabled | İlgili hizmet paketinizde açık değil |
| 413 | payload_too_large | İsteği parçalara bölün (ör. add_leads en fazla 5.000 kayıt) |
| 429 | rate_limited / tenant_rate_limited | Retry-After kadar bekleyin |
| 429 | ip_locked | Bu IP'den çok sayıda hatalı deneme geldi; hatalı yapılandırmayı düzeltin, kilit kendiliğinden kalkar |
Hata cevabı örneği:
{ "ok": false, "error": "Bu anahtarın bu API için yetkisi yok.", "code": "scope_denied", "request_id": "df5fca3a599653f6" }
Hız sınırları
| Sınır | Varsayılan |
|---|---|
| Anahtar başına | dakikada 120 istek (anahtar ayarından düşürülebilir) |
| Hesabın tüm anahtarları toplamı | dakikada 600 istek |
Temsilci durumları (agent_statuses) | ayrıca hesap başına dakikada 2 istek |
Her başarılı cevapta kalan hak başlıklarda gelir:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1790802120
429 alınca Retry-After (saniye) kadar bekleyin. Ağ hatası ve 5xx için üstel geri çekilme kullanın (1 sn, 2 sn, 4 sn… en fazla 5 deneme). 401/403 hatalarını tekrar denemeyin: yapılandırma hatasıdır, denemeler kaba kuvvet kilidini tetikler.
Anahtar yenileme
Anahtarları 90-180 günde bir, personel ayrıldığında ya da sızma şüphesinde yenileyin. Kesintisiz geçiş:
- API Anahtarları > ilgili anahtar > Ayarlar > Anahtarı yenile. "Eskisi 24 saat çalışsın" seçin.
- Aynı ayarlarla yeni anahtar ve yeni imza sırrı üretilir, ekranda bir kez gösterilir.
- Entegrasyonunuzu yeni değerlerle güncelleyin.
- Günlükte eski anahtarın öneki (
bt_7820d84…) artık görünmüyorsa bekleyin; süre dolunca eski anahtar kendiliğinden kapanır.
Sızma şüphesinde "Eskisini hemen kapat" seçin; eski anahtarla gelen istekler anında reddedilir.
İstek günlüğü
API Anahtarları sayfasının altındaki İstek günlüğü her isteği gösterir: zaman, anahtar öneki, IP, uç ve eylem, HTTP durumu, hata kodu, süre, imzalı mı. Son 24 saatin özeti (istek, hata, hız sınırı, kimlik hatası, imzalı istek, farklı IP sayısı) sayfanın üstündedir. Kayıtlar 90 gün saklanır.
Her cevapta X-Request-Id başlığı vardır. Destek talebinde bu değeri yazın; isteğinizi günlükte hemen buluruz. Anahtarı ya da imza sırrını destek talebine, e-postaya ya da ekran görüntüsüne asla koymayın.
Tanımadığınız bir IP ya da beklemediğiniz saatlerde istek görürseniz anahtarı hemen "Eskisini hemen kapat" ile yenileyin.
Byfix CRM kurulumu
Byfix CRM Buluthat'a iki ayrı kimlikle bağlanır:
| Ayar (CRM) | Değer |
|---|---|
| Ayarlar > Otomatik Arama > API Anahtarı | Buluthat'ta üretilen bt_… anahtarı (kapsam: autocall + voicebot + voice_otp + call) |
| Ayarlar > Otomatik Arama > İmza Sırrı | Aynı anahtarın bts_… sırrı. Doluysa CRM'in Buluthat'a attığı her istek imzalanır |
| VoIP ayarları > Buluthat API Token | Canlı ekran / CDR köprüsü (crm_bridge); Buluthat ekibi verir, yukarıdaki anahtardan ayrıdır |
Önerilen sıra: anahtarı İmzalı istek zorunlu kapalı üretin, CRM'e anahtarı ve sırrı girin, günlükte isteklerin imzalı rozetiyle geldiğini görün, sonra anahtarda imzayı zorunlu yapın. Anahtara CRM sunucusunun IP'sini de ekleyin.
Tıkla-ara (begin_call) artık anahtarı URL'de değil başlıkta gönderir. CRM güncellemesinden sonra eski anahtardaki "URL'de anahtar" iznini kapatabilirsiniz.
ByCRM kurulumu
ByCRM'de her firma Buluthat'a kendi anahtarıyla bağlanır; firmalar birbirinin verisini göremez.
- Buluthat panelinde firmanın hesabıyla API Anahtarları > Yeni anahtar: yetkiler Canlı Santral Verisi, Çağrı Kontrolü ve Kuyruklar, Otomatik Arama (kullanılıyorsa Sesli Asistan, Sesli Doğrulama Kodu). ByCRM sunucusunun IP'sini yazın.
- ByCRM > Entegrasyonlar > Buluthat:
| Alan | Değer |
|---|---|
| Buluthat API Token | bt_… |
| İmza sırrı | bts_… |
| Bridge Token | aynı bt_… anahtarı |
| 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 | Kullanılmaz; anahtar hangi hesaba aitse o hesabın verisi gelir |
- Canlı ekranı ve tıkla-arayı deneyin, günlükte
imzalırozetlerini görün, sonra anahtarda İmzalı istek zorunlu'yu açın.
Canlı ekran köprüyü birkaç saniyede bir yokladığı için köprü isteklerinin ayrı ve daha geniş bir hız sınırı vardır (anahtar başına dakikada 1.200); günlüğe yalnızca hatalı köprü istekleri yazılır.
Webhook'ların doğrulanması
Buluthat'ın size gönderdiği webhook'lar da imzalıdır (X-Buluthat-Signature: sha256=…). Sunucunuzda imzayı ham gövde üzerinden doğrulamadan hiçbir webhook'u işlemeyin; X-Buluthat-Delivery ile aynı teslimatı iki kez işlemeyin. Ayrıntı: Webhook'lar.
Güvenlik kontrol listesi
- [ ] Her entegrasyonun kendi anahtarı var, yalnızca gereken kapsamlarla
- [ ] Anahtarlar kodda değil ortam değişkeninde ya da gizli yönetimde (
.envGit'e girmez) - [ ] Anahtar yalnızca sunucu tarafında; tarayıcı JavaScript'inde, mobil uygulamada, Excel makrosunda yok
- [ ] İzinli IP listesi dolu
- [ ] İmzalı istek zorunlu açık
- [ ] URL'de anahtar (
?key=) kapalı - [ ] Son kullanma tarihi var ya da takvimde yenileme hatırlatması kurulu
- [ ] İstemci TLS sertifikasını doğruluyor (
CURLOPT_SSL_VERIFYPEERaçık); doğrulama kapalıyken araya giren biri isteği okuyup değiştirebilir - [ ] Sunucu saati NTP ile eşitli
- [ ] Webhook imzası doğrulanıyor
- [ ] Günlük ayda bir gözden geçiriliyor; ayrılan personelin eriştiği anahtarlar yenilendi
Güvenlik açığı bildirimi
Buluthat API'sinde bir güvenlik açığı bulduğunuzu düşünüyorsanız panel içindeki Destek Merkezi'nden "Güvenlik bildirimi" konulu kayıt açın. Açığı nasıl yeniden ürettiğinizi ve varsa X-Request-Id değerlerini ekleyin. Bildirimi inceleyip düzeltene kadar ayrıntıları paylaşmamanızı rica ederiz.
