API-beveiliging
De Buluthat API beheert uw telefooncentrale: ze start oproepen, beëindigt gesprekken, verstuurt SMS en verwerkt klantnummers. Een gelekte sleutel betekent daarom niet "een rapport is zichtbaar", maar "er wordt vanuit uw account gebeld". Deze pagina legt uit hoe u uw sleutel beschermt en welke beveiligingen Buluthat voor u toepast.
Basisadres: https://api.buluthat.com/api/
De lagen in één oogopslag
Elk verzoek passeert deze poorten in volgorde. Wordt het door één geweigerd, dan wordt het niet verwerkt en in het logboek vastgelegd.
| Volgorde | Poort | Wat het doet | Fout |
|---|---|---|---|
| 1 | Brute-force-vergrendeling | Probeert een IP binnen 10 minuten 20 keer een onjuiste sleutel of handtekening, dan wordt dat IP tijdelijk geblokkeerd | 429 ip_locked |
| 2 | Body-limiet | Een verzoek-body groter dan 5 MB wordt niet gelezen | 413 payload_too_large |
| 3 | Sleutel | De sleutel wordt alleen in de header geaccepteerd; in het systeem wordt alleen de SHA-256-hash bewaard | 401 invalid_token |
| 4 | Duur en opzegging | Een verlopen of ingetrokken sleutel wordt geweigerd | 401 token_expired |
| 5 | Toegestaan IP | Is voor de sleutel een IP-lijst gedefinieerd, dan komen alleen verzoeken van die adressen door | 403 ip_not_allowed |
| 6 | Accountstatus | Sleutels van een gesloten of inactief account werken niet | 403 account_inactive |
| 7 | Bereik | De sleutel krijgt alleen toegang tot toegestane API's | 403 scope_denied |
| 8 | Handtekening | Staat "ondertekend verzoek verplicht" aan, dan worden HMAC-handtekening, tijdstempel en eenmalige nonce gecontroleerd | 401 signature_* |
| 9 | Snelheidslimiet | Minuutlimiet per sleutel en voor het accounttotaal | 429 rate_limited |
Snelstart
- In het paneel Account en Support > API-sleutels opent de pagina (alleen zichtbaar voor de accountbeheerder).
- Nieuwe sleutel: geef een naam, vink alleen de benodigde rechten aan, vul het uitgaande IP van uw server in, Ondertekend verzoek verplichtte openen.
- Kopieer de twee waarden die eenmalig op het scherm worden getoond: API-sleutel (
bt_…, bij elk verzoekAuthorizationwordt in de header verstuurd) en handtekeningsleutel (bts_…, wordt gebruikt om het verzoek te ondertekenen, wordt nooit verzonden). - Probeer de verbinding:
curl "https://api.buluthat.com/api/autocall.php?action=ping" \
-H "Authorization: Bearer bt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Is een handtekening verplicht, dan dit verzoek 401 signature_required geeft terug; de onderstaande Verzoekondertekening gebruik een van de voorbeelden in het onderdeel.
Sleutel en handtekeningsleutel zijn in het systeem niet terug te lezen wordt niet bewaard. Als u deze kwijtraakt, kunnen wij hem niet terughalen; u vernieuwt de sleutel dan in het paneel.
Bereiken
Elke sleutel wordt met één of meer bereiken gegenereerd. Een verzoek aan een API buiten het bereik 403 scope_denied geeft terug.
| Bereik | API's die het opent |
|---|---|
autocall | Automatisch bellen (api/autocall.php). Voor compatibiliteit met oudere integraties opent het ook de endpoints voor spraakassistent, spraakverificatie en gespreksbesturing |
call | Gespreksbesturing en wachtrijen: begin_call, bridge, hangup, transfer, mute, queues, queues_pending, queue_user_list, queue_manage_users, agent_statuses |
voicebot | Spraakassistent (api/voicebot_api.php) en spraakverificatie |
voice_otp | Spraakverificatiecode (api/voice_otp.php) |
sms | SMS API (api/sms.php). Geen enkel ander bereik heeft toegang tot SMS |
bridge | Live telefooncentralegegevens (api/crm_bridge.php): live oproepen, medewerkerstatus, gespreksopnamen, geluidsopname, zwarte lijst, aankondigingen. Alleen het account van de sleutel; verzonden tenant_id wordt genegeerd |
* | Alle API's. Alleen als het echt nodig is |
Principe: een aparte sleutel per integratie, de minste rechten per sleutel. Lekt de SMS-sleutel van uw webshop, dan kan een aanvaller geen oproepen starten; u trekt alleen die ene sleutel in.
Sleutel niet meesturen
Sleutel in de header wordt verzonden:
Authorization: Bearer bt_xxxxxxxx
Authorization voor omgevingen die de header niet kunnen instellen X-Api-Key: bt_xxxxxxxx wordt ook geaccepteerd.
Sleutel in de URL (?key=)
?key=bt_… formaat bij nieuwe sleutels is uitgeschakeld en 401 query_key_disabled komt terecht in logbestanden van de webserver, proxylogs, browsergeschiedenis en Referer komt in de header terecht; de sleutel lekt daar weg. Alleen voor een oud systeem dat geen header kan sturen, in de sleutelinstellingen "Sleutel in URL accepteren" kan worden geopend. Het paneel markeert deze sleutels rood ?key= toont met de badge.
Voor sleutels die vóór V54 zijn aangemaakt is deze toestemming open gelaten, zodat oude integraties niet breken. Sluit hem nadat u uw integratie naar de header hebt verplaatst.
Toegestaan IP
In de sleutelinstellingen Toegestane IP-adressen in het veld schrijft u één IP of CIDR-blok per regel:
85.111.89.220
10.20.0.0/24
2a01:4f8:c0c:1234::/64
Is de lijst leeg, dan wordt elk IP geaccepteerd. Is hij gevuld, dan wordt een verzoek van buiten de adressen in de lijst, ook met de juiste sleutel, 403 ip_not_allowed geeft terug. Het adres waarnaar geschreven wordt, is het adres van degene die de API aanroept is het uitgaande IP van uw server (niet die van uw eigen computer). Als u het niet zeker weet, kijk dan in het logboek naar de IP-kolom van het verzoek van die server.
Verzoekondertekening
De handtekening zorgt ervoor dat er geen verzoeken kunnen worden gedaan, zelfs als de sleutel in verkeerde handen valt: de aanvaller heeft ook de handtekeningsleutel nodig en die gaat bij geen enkel verzoek over het netwerk. De handtekening ook:
- Vergrendelt de body: verandert er één teken in het pad, dan klopt de handtekening niet.
- Voorkomt herhaald afspelen: elke nonce wordt maar één keer geaccepteerd; een onderschept verzoek kan niet nogmaals worden verzonden.
- Weigert verouderde verzoeken: wijkt het tijdstempel meer dan ±5 minuten af van de servertijd, dan wordt het verzoek geweigerd.
Bij de sleutel Ondertekend verzoek verplicht is aan, dan moet elk verzoek ondertekend zijn. Ook als het uit staat, wordt de handtekening alsnog gecontroleerd wanneer u handtekeningheaders meestuurt; een verkeerde handtekening komt niet ongemerkt door.
Headers
| Afzendernaam | Waarde |
|---|---|
Authorization | Bearer bt_… |
X-Bt-Timestamp | Unixtijd, seconden (bijv. 1790802088) |
X-Bt-Nonce | Bij elk verzoek nieuw, 16-64 tekens A-Z a-z 0-9 _ - (bijv. 32 hex) |
X-Bt-Signature | v1= + handtekening in hex, kleine letters |
Canonieke tekst
De ondertekende tekst, tussen \n (LF), zes regels:
v1
POST
/api/sms.php?action=send
1790802088
9f86d081884c7d659a2feaa0c55ad015
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
| Regel | Inhoud |
|---|---|
| 1 | Versie, vast v1 |
| 2 | HTTP-methode, in hoofdletters (GET, POST) |
| 3 | Pad en querystring, zoals het verzonden werd (/api/sms.php?action=send). Domeinnaam en schema zijn niet inbegrepen |
| 4 | X-Bt-Timestamp waarde |
| 5 | X-Bt-Nonce waarde |
| 6 | SHA-256-hash van de body, in hex met kleine letters. Bij een verzoek zonder body en multipart/form-data Samenvatting van een lege body bij (bestands)uploadverzoeken: e3b0c442…b855 |
Handtekening: 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'],
]));
Kant-en-klare clients ondertekenen ook: buluthat-autocall-client.php en buluthat-voice-otp-client.php in de vierde parameter ['signing_secret' => 'bts_…'] ontvangt.
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"}]}))
Opdrachtregel (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"
Waarom klopt de handtekening niet?
| Symptoom | Reden |
|---|---|
signature_invalid alleen bij POST | De body die u ondertekende verschilt van de body die u verstuurde. De JSON eenmalig genereer, geef dezelfde variabele aan zowel de samenvatting als het verzoek |
signature_invalid Bij een opvraag met Turkse tekens | U hebt het pad zelf samengesteld en anders gecodeerd. Gebruik in de handtekening het pad en de query uit de URL die de client daadwerkelijk verstuurt (parse_url / new URL()) |
signature_expired | De klok van uw server loopt achter. NTP (timedatectl set-ntp true) openen; tolerantie ±300 sec |
signature_replayed | Zelfde nonce werd twee keer verstuurd. Bij een nieuwe poging (retry) de nonce en tijdstempel opnieuw genereren en onderteken opnieuw |
signature_required | Handtekening is verplicht voor de sleutel, maar headers ontbreken |
signature_not_configured | De sleutel heeft geen handtekeningsleutel; genereer in het paneel een "Nieuwe handtekeningsleutel" |
Foutcodes
Identiteits- en beveiligingsfouten zijn in alle JSON-endpoints gelijk code komt terug met de waarden. De endpoints voor gespreksbesturing en wachtrijen (Verimor-compatibel) geven dezelfde HTTP-code met een bericht in platte tekst.
| HTTP | code | Wat te doen |
|---|---|---|
| 401 | missing_token | Authorization: Bearer … voeg de header toe |
| 401 | invalid_token | De sleutel is onjuist, ingetrokken of ontbreekt. Niet opnieuw proberen, corrigeer de instelling |
| 401 | token_expired | Vernieuw de sleutel in het paneel |
| 401 | query_key_disabled | Stuur de sleutel in de header in plaats van in de URL |
| 401 | signature_* | Zie de tabel hierboven |
| 403 | ip_not_allowed | Voeg het uitgaande IP van uw server toe aan de lijst van de sleutel |
| 403 | scope_denied | Geef de sleutel het juiste recht of gebruik de juiste sleutel |
| 403 | account_inactive | Account is gesloten; neem contact op met support |
| 403 | module_disabled | De betreffende dienst is niet ingeschakeld in uw pakket |
| 413 | payload_too_large | Splits het verzoek in delen (bijv. add_leads maximaal 5.000 records) |
| 429 | rate_limited / tenant_rate_limited | Retry-After wacht tot |
| 429 | ip_locked | Vanaf dit IP kwamen veel foutieve pogingen binnen; corrigeer de onjuiste configuratie, de blokkade vervalt vanzelf |
Voorbeeld van een foutantwoord:
{ "ok": false, "error": "Bu anahtarın bu API için yetkisi yok.", "code": "scope_denied", "request_id": "df5fca3a599653f6" }
Snelheidslimieten
| Limiet | Standaard |
|---|---|
| Per sleutel | 120 verzoeken per minuut (te verlagen in de sleutelinstellingen) |
| Totaal van alle sleutels van het account | 600 verzoeken per minuut |
Medewerkerstatussen (agent_statuses) | bovendien 2 verzoeken per minuut per account |
Bij elk geslaagd antwoord staan de resterende rechten in de headers:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1790802120
429 zodra u ontvangt Retry-After (seconden) wachten. Netwerkfout en 5xx gebruik exponentiële terugval (1 sec, 2 sec, 4 sec… maximaal 5 pogingen). 401/403 fouten niet opnieuw proberen: dit is een configuratiefout, pogingen activeren de brute-force-vergrendeling.
Sleutelvernieuwing
Vernieuw sleutels elke 90-180 dagen, wanneer personeel vertrekt of bij vermoeden van een lek. Naadloze overgang:
- API-sleutels > betreffende sleutel > Instellingen > Sleutel vernieuwen. Kies "De oude sleutel nog 24 uur laten werken".
- Met dezelfde instellingen worden een nieuwe sleutel en een nieuwe handtekeningsleutel gegenereerd en eenmalig op het scherm getoond.
- Werk uw integratie bij met de nieuwe waarden.
- In het logboek het voorvoegsel van de oude sleutel (
bt_7820d84…) niet meer zichtbaar is, wacht dan; na afloop van de termijn sluit de oude sleutel vanzelf.
Bij vermoeden van een lek Kies "Oude sleutel direct sluiten"; verzoeken met de oude sleutel worden onmiddellijk geweigerd.
Verzoeklogboek
Onderaan de pagina API-sleutels Verzoeklogboek toont elk verzoek: tijd, voorvoegsel van de sleutel, IP, endpoint en actie, HTTP-status, foutcode, duur, ondertekend of niet. Bovenaan de pagina staat de samenvatting van de laatste 24 uur (verzoeken, fouten, snelheidslimiet, identiteitsfouten, ondertekende verzoeken, aantal verschillende IP's). Logs worden 90 dagen bewaard.
In elk antwoord X-Request-Id header. Vermeld deze waarde in een supportverzoek; wij vinden uw verzoek dan direct in het logboek. Zet de sleutel of de handtekeningsleutel nooit in een supportverzoek, e-mail of schermafbeelding.
Ziet u een IP dat u niet kent of verzoeken op onverwachte tijdstippen, vernieuw de sleutel dan direct met "Oude sleutel direct sluiten".
Installatie van het Byfix CRM
Het Byfix CRM maakt met twee afzonderlijke identiteiten verbinding met Buluthat:
| Instelling (CRM) | Waarde |
|---|---|
| Instellingen > Automatisch bellen > API-sleutel | Gegenereerd in Buluthat bt_… sleutel (bereik: autocall + voicebot + voice_otp + call) |
| Instellingen > Automatisch bellen > Handtekeningsleutel | Dezelfde sleutel bts_… geheim. Is het ingevuld, dan wordt elk verzoek dat het CRM aan Buluthat stuurt ondertekend |
| VoIP-instellingen > Buluthat API-token | Live scherm / CDR-brug (crm_bridge); het Buluthat-team verstrekt deze, los van bovenstaande sleutel |
Aanbevolen volgorde: de sleutel Ondertekend verzoek verplicht maak ze uitgeschakeld aan, voer de sleutel en het geheim in het CRM in en let in het logboek op de verzoeken van imzalı badge komt binnen, maak daarna de handtekening verplicht bij de sleutel. Voeg ook het IP van de CRM-server aan de sleutel toe.
Click-to-call (begin_call) verstuurt de sleutel nu in de header in plaats van in de URL. Na de CRM-update kunt u bij de oude sleutel de toestemming "Sleutel in URL" uitschakelen.
ByCRM-installatie
In ByCRM wordt elk bedrijf aan Buluthat met een eigen sleutel wordt gekoppeld; bedrijven kunnen elkaars gegevens niet zien.
- In het Buluthat-paneel met het account van het bedrijf API-sleutels > Nieuwe sleutel: rechten Live telefooncentralegegevens, Gespreksbesturing en wachtrijen, Automatisch bellen (indien gebruikt Spraakassistent, Spraakverificatiecode). Vul het IP van de ByCRM-server in.
- ByCRM > Integraties > Buluthat:
| Veld | Waarde |
|---|---|
| Buluthat API-token | bt_… |
| Handtekeningsleutel | bts_… |
| Bridge Token | dezelfde bt_… sleutel |
| 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 | Wordt niet gebruikt; u krijgt de gegevens van het account waartoe de sleutel behoort |
- Probeer het live scherm en click-to-call uit, in het logboek
imzalıbadges, daarna bij de sleutel Ondertekend verzoek verplichtte openen.
Omdat het live scherm de brug om de paar seconden bevraagt, hebben brugverzoeken een aparte, ruimere snelheidslimiet (1.200 per minuut per sleutel); in het logboek worden alleen foutieve brugverzoeken vastgelegd.
Webhooks verifiëren
Ook de webhooks die Buluthat naar u verstuurt zijn ondertekend (X-Buluthat-Signature: sha256=…). Controleer op uw server de handtekening ruwe body verwerk geen enkele webhook zonder verificatie via X-Buluthat-Delivery verwerk dezelfde aflevering niet twee keer. Details: Webhooks.
Beveiligingschecklist
- [ ] Elke integratie heeft een eigen sleutel, alleen met de benodigde bereiken
- [ ] Sleutels staan niet in de code maar in omgevingsvariabelen of geheimenbeheer (
.envKomt niet in Git) - [ ] De sleutel staat alleen aan de serverzijde; niet in browser-JavaScript, mobiele app of Excel-macro
- [ ] De lijst met toegestane IP's is gevuld
- [ ] Ondertekend verzoek verplicht staat aan
- [ ] Sleutel in de URL (
?key=) uit - [ ] Er is een vervaldatum of een herinnering voor vernieuwing in de agenda
- [ ] De client controleert het TLS-certificaat (
CURLOPT_SSL_VERIFYPEERaan); is verificatie uitgeschakeld, dan kan iemand daartussen het verzoek lezen en wijzigen - [ ] De servertijd is gesynchroniseerd met NTP
- [ ] De webhook-handtekening wordt gecontroleerd
- [ ] Het logboek wordt maandelijks bekeken; sleutels waartoe vertrokken personeel toegang had zijn vernieuwd
Melding van een beveiligingslek
Denkt u een beveiligingslek in de Buluthat API te hebben gevonden, meld dit dan via het Helpcentrum in het paneel "Beveiligingsmelding" open een melding met het onderwerp. Beschrijf hoe u het lek hebt gereproduceerd en, indien aanwezig, X-Request-Id voeg de waarden toe. Wij verzoeken u de details niet te delen tot wij de melding hebben beoordeeld en verholpen.
