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.

VolgordePoortWat het doetFout
1Brute-force-vergrendelingProbeert een IP binnen 10 minuten 20 keer een onjuiste sleutel of handtekening, dan wordt dat IP tijdelijk geblokkeerd429 ip_locked
2Body-limietEen verzoek-body groter dan 5 MB wordt niet gelezen413 payload_too_large
3SleutelDe sleutel wordt alleen in de header geaccepteerd; in het systeem wordt alleen de SHA-256-hash bewaard401 invalid_token
4Duur en opzeggingEen verlopen of ingetrokken sleutel wordt geweigerd401 token_expired
5Toegestaan IPIs voor de sleutel een IP-lijst gedefinieerd, dan komen alleen verzoeken van die adressen door403 ip_not_allowed
6AccountstatusSleutels van een gesloten of inactief account werken niet403 account_inactive
7BereikDe sleutel krijgt alleen toegang tot toegestane API's403 scope_denied
8HandtekeningStaat "ondertekend verzoek verplicht" aan, dan worden HMAC-handtekening, tijdstempel en eenmalige nonce gecontroleerd401 signature_*
9SnelheidslimietMinuutlimiet per sleutel en voor het accounttotaal429 rate_limited

Snelstart

  1. In het paneel Account en Support > API-sleutels opent de pagina (alleen zichtbaar voor de accountbeheerder).
  2. Nieuwe sleutel: geef een naam, vink alleen de benodigde rechten aan, vul het uitgaande IP van uw server in, Ondertekend verzoek verplichtte openen.
  3. Kopieer de twee waarden die eenmalig op het scherm worden getoond: API-sleutel (bt_…, bij elk verzoek Authorization wordt in de header verstuurd) en handtekeningsleutel (bts_…, wordt gebruikt om het verzoek te ondertekenen, wordt nooit verzonden).
  4. 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.

BereikAPI's die het opent
autocallAutomatisch bellen (api/autocall.php). Voor compatibiliteit met oudere integraties opent het ook de endpoints voor spraakassistent, spraakverificatie en gespreksbesturing
callGespreksbesturing en wachtrijen: begin_call, bridge, hangup, transfer, mute, queues, queues_pending, queue_user_list, queue_manage_users, agent_statuses
voicebotSpraakassistent (api/voicebot_api.php) en spraakverificatie
voice_otpSpraakverificatiecode (api/voice_otp.php)
smsSMS API (api/sms.php). Geen enkel ander bereik heeft toegang tot SMS
bridgeLive 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

AfzendernaamWaarde
AuthorizationBearer bt_…
X-Bt-TimestampUnixtijd, seconden (bijv. 1790802088)
X-Bt-NonceBij elk verzoek nieuw, 16-64 tekens A-Z a-z 0-9 _ - (bijv. 32 hex)
X-Bt-Signaturev1= + 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
RegelInhoud
1Versie, vast v1
2HTTP-methode, in hoofdletters (GET, POST)
3Pad en querystring, zoals het verzonden werd (/api/sms.php?action=send). Domeinnaam en schema zijn niet inbegrepen
4X-Bt-Timestamp waarde
5X-Bt-Nonce waarde
6SHA-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?

SymptoomReden
signature_invalid alleen bij POSTDe 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 tekensU 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_expiredDe klok van uw server loopt achter. NTP (timedatectl set-ntp true) openen; tolerantie ±300 sec
signature_replayedZelfde nonce werd twee keer verstuurd. Bij een nieuwe poging (retry) de nonce en tijdstempel opnieuw genereren en onderteken opnieuw
signature_requiredHandtekening is verplicht voor de sleutel, maar headers ontbreken
signature_not_configuredDe 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.

HTTPcodeWat te doen
401missing_tokenAuthorization: Bearer … voeg de header toe
401invalid_tokenDe sleutel is onjuist, ingetrokken of ontbreekt. Niet opnieuw proberen, corrigeer de instelling
401token_expiredVernieuw de sleutel in het paneel
401query_key_disabledStuur de sleutel in de header in plaats van in de URL
401signature_*Zie de tabel hierboven
403ip_not_allowedVoeg het uitgaande IP van uw server toe aan de lijst van de sleutel
403scope_deniedGeef de sleutel het juiste recht of gebruik de juiste sleutel
403account_inactiveAccount is gesloten; neem contact op met support
403module_disabledDe betreffende dienst is niet ingeschakeld in uw pakket
413payload_too_largeSplits het verzoek in delen (bijv. add_leads maximaal 5.000 records)
429rate_limited / tenant_rate_limitedRetry-After wacht tot
429ip_lockedVanaf 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

LimietStandaard
Per sleutel120 verzoeken per minuut (te verlagen in de sleutelinstellingen)
Totaal van alle sleutels van het account600 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:

  1. API-sleutels > betreffende sleutel > Instellingen > Sleutel vernieuwen. Kies "De oude sleutel nog 24 uur laten werken".
  2. Met dezelfde instellingen worden een nieuwe sleutel en een nieuwe handtekeningsleutel gegenereerd en eenmalig op het scherm getoond.
  3. Werk uw integratie bij met de nieuwe waarden.
  4. 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-sleutelGegenereerd in Buluthat bt_… sleutel (bereik: autocall + voicebot + voice_otp + call)
Instellingen > Automatisch bellen > HandtekeningsleutelDezelfde sleutel bts_… geheim. Is het ingevuld, dan wordt elk verzoek dat het CRM aan Buluthat stuurt ondertekend
VoIP-instellingen > Buluthat API-tokenLive 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.

  1. 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.
  2. ByCRM > Integraties > Buluthat:
VeldWaarde
Buluthat API-tokenbt_…
Handtekeningsleutelbts_…
Bridge Tokendezelfde bt_… sleutel
CRM Bridge URLhttps://api.buluthat.com/api/crm_bridge.php
Autocall URLhttps://api.buluthat.com/api/autocall.php
Buluthat Tenant ID / PBX Server IDWordt niet gebruikt; u krijgt de gegevens van het account waartoe de sleutel behoort
  1. 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 (.env Komt 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_VERIFYPEER aan); 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.