API-Sicherheit

Die Buluthat-API steuert Ihre Telefonanlage: Sie startet Anrufe, beendet Gespräche, sendet SMS und verarbeitet Kundennummern. Ein Schlüsselleck bedeutet daher nicht „ein Bericht wird sichtbar“, sondern „von Ihrem Konto aus wird telefoniert“. Diese Seite beschreibt, wie Sie Ihren Schlüssel schützen und welche Schutzmaßnahmen Buluthat für Sie umsetzt.

Basisadresse: https://api.buluthat.com/api/

Die Ebenen auf einen Blick

Jede Anfrage durchläuft nacheinander diese Prüfstufen. Lehnt eine ab, wird die Anfrage nicht verarbeitet und ins Protokoll geschrieben.

ReihenfolgeStufeWas es tutFehler
1Brute-Force-SperreVersucht eine IP 10 Mal in 20 Minuten einen falschen Schlüssel oder eine falsche Signatur, wird diese IP vorübergehend gesperrt429 ip_locked
2Body-LimitAnfrage-Bodys über 5 MB werden nicht gelesen413 payload_too_large
3SchlüsselDer Schlüssel wird nur im Header akzeptiert; im System wird nur der SHA-256-Hash gespeichert401 invalid_token
4Dauer und KündigungEin abgelaufener oder widerrufener Schlüssel wird abgelehnt401 token_expired
5Zugelassene IPErteilen Sie dem Schlüssel die entsprechende Berechtigung oder verwenden Sie den richtigen Schlüssel403 ip_not_allowed
6KontostatusSchlüssel von geschlossenen oder inaktiven Konten funktionieren nicht403 account_inactive
7GeltungsbereichDer Schlüssel erhält nur Zugriff auf freigegebene APIs403 scope_denied
8SignaturIst beim Schlüssel „Signierte Anfrage erforderlich“ aktiv, werden HMAC-Signatur, Zeitstempel und einmalige Nonce geprüft401 signature_*
9RatenbegrenzungMinutenlimit pro Schlüssel und für die Kontosumme429 rate_limited

Schnellstart

  1. Im Panel Konto und Support > API-Schlüssel Öffnen Sie die Seite (nur für den Kontobevollmächtigten sichtbar).
  2. Neuer Schlüssel: Geben Sie einen Namen vor, markieren Sie nur die benötigten Berechtigungen, tragen Sie die Ausgangs-IP Ihres Servers ein, Signierte Anfrage erforderlichöffnen.
  3. Kopieren Sie die beiden einmalig angezeigten Werte: API-Schlüssel (bt_…, bei jeder Anfrage Authorization im Header gesendet) und Signaturgeheimnis (bts_…, dient zum Signieren der Anfrage, wird nie gesendet).
  4. Testen Sie die Verbindung:
curl "https://api.buluthat.com/api/autocall.php?action=ping" \
  -H "Authorization: Bearer bt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Ist die Signatur verpflichtend, diese Anfrage 401 signature_required kommt zurück; die folgende Anfragesignierung Verwenden Sie eines der Beispiele im Abschnitt.

Schlüssel und Signaturgeheimnis sind im System nicht in lesbarer Form wird nicht gespeichert. Bei Verlust können wir ihn nicht wiederherstellen; Sie erneuern den Schlüssel im Panel.

Geltungsbereiche

Jeder Schlüssel wird mit einem oder mehreren Geltungsbereichen erzeugt. Eine Anfrage an eine API außerhalb des Geltungsbereichs 403 scope_denied kommt zurück.

GeltungsbereichAPIs, die er öffnet
autocallAuto-Call (api/autocall.php). Aus Kompatibilitätsgründen mit älteren Integrationen öffnet er auch die Endpunkte für Sprachassistent, Sprach-Verifizierung und Anrufsteuerung
callAnrufsteuerung und Warteschlangen: begin_call, bridge, hangup, transfer, mute, queues, queues_pending, queue_user_list, queue_manage_users, agent_statuses
voicebotSprachassistent (api/voicebot_api.php) und Sprach-Verifizierung
voice_otpSprach-Bestätigungscode (api/voice_otp.php)
smsSMS-API (api/sms.php). Kein anderer Geltungsbereich hat Zugriff auf SMS
bridgeLive-Telefonanlagendaten (api/crm_bridge.php): Live-Anrufe, Agentenstatus, Gesprächsaufzeichnungen, Tonaufnahmen, Sperrliste, Ansagen. Nur das Konto des Schlüssels; gesendete tenant_id wird ignoriert
*Alle APIs. Nur wenn wirklich nötig

Grundsatz: ein eigener Schlüssel pro Integration, jedem Schlüssel die geringsten Rechte. Gelangt der SMS-Schlüssel Ihres Onlineshops nach außen, kann ein Angreifer keine Anrufe starten; Sie widerrufen nur diesen Schlüssel.

Schlüssel senden

Schlüssel im Header wird gesendet:

Authorization: Bearer bt_xxxxxxxx

Authorization für Umgebungen, die den Header nicht setzen können X-Api-Key: bt_xxxxxxxx wird ebenfalls akzeptiert.

Schlüssel in der URL (?key=)

?key=bt_… Format bei neuen Schlüsseln ist deaktiviert und 401 query_key_disabled kommt zurück. URLs landen in Webserver-Protokollen, Proxy-Aufzeichnungen, im Browserverlauf und Referer landet im Header; darüber gelangt der Schlüssel nach außen. Nur für ein altes System, das keinen Header senden kann, in den Schlüsseleinstellungen "Schlüssel in der URL akzeptieren" geöffnet werden. Das Panel markiert diese Schlüssel rot ?key= mit dem Badge an.

Bei vor V54 erzeugten Schlüsseln wurde diese Berechtigung offen gelassen, damit ältere Integrationen nicht abreißen. Schließen Sie sie, nachdem Sie Ihre Integration auf den Header umgestellt haben.

Zugelassene IP

In den Schlüsseleinstellungen Zugelassene IP-Adressen Im Feld wird pro Zeile eine IP oder ein CIDR-Block eingetragen:

85.111.89.220
10.20.0.0/24
2a01:4f8:c0c:1234::/64

Ist die Liste leer, wird jede IP akzeptiert. Ist sie gefüllt, wird eine Anfrage von einer Adresse außerhalb der Liste auch bei richtigem Schlüssel 403 ip_not_allowed kommt zurück. Die einzutragende Adresse ist die, die die API aufruft ist die Ausgangs-IP Ihres Servers (nicht die Ihres eigenen Rechners). Wenn Sie unsicher sind, prüfen Sie im Protokoll die IP-Spalte der Anfrage von diesem Server.

Anfragesignierung

Die Signatur sorgt dafür, dass selbst bei Übernahme des Schlüssels keine Anfragen gestellt werden können: Der Angreifer braucht zusätzlich das Signaturgeheimnis, das bei keiner Anfrage über das Netz geht. Die Signatur außerdem:

  • Sperrt den Body: ändert sich auch nur ein Zeichen im Pfad, stimmt die Signatur nicht.
  • Verhindert die Wiedergabe erneut: jede Nonce wird nur einmal akzeptiert; eine abgefangene Anfrage kann kein zweites Mal gesendet werden.
  • Lehnt veraltete Anfragen ab: weicht der Zeitstempel um mehr als ±5 Minuten von der Serverzeit ab, wird die Anfrage abgelehnt.

Beim Schlüssel Signierte Anfrage erforderlich Ist die Option aktiv, muss jede Anfrage signiert sein. Auch wenn sie deaktiviert ist, wird die Signatur geprüft, sobald Sie Signatur-Header mitsenden; eine falsche Signatur geht nicht stillschweigend durch.

Header

AbsenderkennungWert
AuthorizationBearer bt_…
X-Bt-TimestampUnix-Zeit, Sekunden (z. B. 1790802088)
X-Bt-NonceBei jeder Anfrage neu, 16-64 Zeichen A-Z a-z 0-9 _ - (z. B. 32 hex)
X-Bt-Signaturev1= + die Signatur in Kleinbuchstaben-Hex

Kanonischer Text

Der signierte Text, dazwischen \n (LF) sind es sechs Zeilen:

v1
POST
/api/sms.php?action=send
1790802088
9f86d081884c7d659a2feaa0c55ad015
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
ZeileInhalt
1Version, fest v1
2HTTP-Methode, Großbuchstaben (GET, POST)
3Pfad und Query-String, so, wie die Anfrage gesendet wurde (/api/sms.php?action=send). Domain und Schema sind nicht enthalten
4X-Bt-Timestamp Wert
5X-Bt-Nonce Wert
6SHA-256-Hash des Bodys, Hex in Kleinbuchstaben. Bei Anfragen ohne Body und multipart/form-data Bei (Datei-Upload-)Anfragen der Hash des leeren Textes: e3b0c442…b855

Signatur: 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'],
]));

Auch die fertigen Clients signieren: buluthat-autocall-client.php und buluthat-voice-otp-client.php im vierten Parameter ['signing_secret' => 'bts_…'] erhält.

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"}]}))

Kommandozeile (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"

Warum stimmt die Signatur nicht?

SymptomGrund
signature_invalid nur bei POSTDer signierte Body und der gesendete Body unterscheiden sich. JSON einmalig erzeugen, dieselbe Variable in Zusammenfassung und Anfrage übergeben
signature_invalid Bei einer Abfrage mit türkischen ZeichenSie haben den Pfad selbst zusammengesetzt und anders kodiert. Verwenden Sie in der Signatur den Pfad und die Query aus der URL, die der Client tatsächlich gesendet hat (parse_url / new URL())
signature_expiredDie Uhr Ihres Servers geht falsch. NTP (timedatectl set-ntp true) öffnen; Toleranz ±300 Sek.
signature_replayedDieselbe Nonce wurde ein zweites Mal gesendet. Bei einem erneuten Versuch (Retry) Nonce und Zeitstempel neu erzeugen und neu signieren
signature_requiredBeim Schlüssel ist eine Signatur erforderlich, aber die Header fehlen
signature_not_configuredDer Schlüssel hat kein Signaturgeheimnis; erzeugen Sie im Panel ein „Neues Signaturgeheimnis“

Fehlercodes

Authentifizierungs- und Sicherheitsfehler sind bei allen JSON-Endpunkten gleich code kommt mit den Werten zurück. Die Endpunkte für Anrufsteuerung und Warteschlangen (Verimor-kompatibel) liefern denselben HTTP-Code mit einer Klartextmeldung.

HTTPcodeWas zu tun ist
401missing_tokenAuthorization: Bearer … fügen Sie den Header hinzu
401invalid_tokenSchlüssel falsch, widerrufen oder nicht vorhanden. Nicht erneut versuchen, korrigieren Sie die Einstellung
401token_expiredSchlüssel im Panel erneuern
401query_key_disabledSenden Sie den Schlüssel im Header statt in der URL
401signature_*Siehe die Tabelle oben
403ip_not_allowedTragen Sie die Ausgangs-IP Ihres Servers in die Liste des Schlüssels ein
403scope_deniedBeim Schlüssel
403account_inactiveKonto gesperrt; wenden Sie sich an den Support
403module_disabledDer entsprechende Dienst ist in Ihrem Paket nicht aktiviert
413payload_too_largeTeilen Sie die Anfrage in Teile auf (z. B. add_leads höchstens 5.000 Einträge)
429rate_limited / tenant_rate_limitedRetry-After warten Sie bis
429ip_lockedVon dieser IP gingen viele fehlerhafte Versuche ein; korrigieren Sie die fehlerhafte Konfiguration, die Sperre hebt sich von selbst auf

Beispiel für eine Fehlerantwort:

{ "ok": false, "error": "Bu anahtarın bu API için yetkisi yok.", "code": "scope_denied", "request_id": "df5fca3a599653f6" }

Ratenbegrenzungen

LimitStandard
Pro Schlüssel120 Anfragen pro Minute (in den Schlüsseleinstellungen senkbar)
Summe aller Schlüssel des Kontos600 Anfragen pro Minute
Agentenstatus (agent_statuses)zusätzlich 2 Anfragen pro Minute und Konto

Bei jeder erfolgreichen Antwort kommt das verbleibende Kontingent in den Headern:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1790802120

429 bei Erhalt Retry-After (Sekunden) abwarten. Bei Netzwerkfehlern und 5xx verwenden Sie für exponentiellen Backoff (1 Sek., 2 Sek., 4 Sek. … höchstens 5 Versuche). 401/403 Fehler nicht erneut versuchen: das ist ein Konfigurationsfehler, Versuche lösen die Brute-Force-Sperre aus.

Schlüssel fehlt, ist ungültig oder abgelaufen

Erneuern Sie Schlüssel alle 90-180 Tage, bei Weggang von Mitarbeitern oder bei Verdacht auf ein Leck. Unterbrechungsfreier Wechsel:

  1. API-Schlüssel > betreffender Schlüssel > Einstellungen > Schlüssel erneuern. Wählen Sie „Alter Schlüssel läuft noch 24 Stunden“.
  2. Mit denselben Einstellungen werden ein neuer Schlüssel und ein neues Signaturgeheimnis erzeugt und einmalig auf dem Bildschirm angezeigt.
  3. Aktualisieren Sie Ihre Integration mit den neuen Werten.
  4. Im Protokoll das Präfix des alten Schlüssels (bt_7820d84…) nicht mehr angezeigt wird, warten Sie; nach Ablauf der Frist schließt sich der alte Schlüssel von selbst.

Bei Verdacht auf ein Leck Wählen Sie „Alten Schlüssel sofort schließen“; Anfragen mit dem alten Schlüssel werden sofort abgelehnt.

Anfrageprotokoll

Unterhalb der Seite „API-Schlüssel“ Anfrageprotokoll zeigt jede Anfrage: Zeitpunkt, Schlüsselpräfix, IP, Endpunkt und Aktion, HTTP-Status, Fehlercode, Dauer, signiert oder nicht. Die Zusammenfassung der letzten 24 Stunden (Anfragen, Fehler, Ratenbegrenzung, Authentifizierungsfehler, signierte Anfragen, Anzahl verschiedener IPs) steht oben auf der Seite. Einträge werden 90 Tage aufbewahrt.

In jeder Antwort X-Request-Id gibt es. Geben Sie diesen Wert in der Supportanfrage an; wir finden Ihre Anfrage sofort im Protokoll. Geben Sie den Schlüssel oder das Signaturgeheimnis niemals in Supportanfragen, E-Mails oder Screenshots weiter.

Sehen Sie Anfragen von einer unbekannten IP oder zu unerwarteten Zeiten, erneuern Sie den Schlüssel sofort mit „Alten sofort schließen“.

Einrichtung des Byfix-CRM

Das Byfix-CRM verbindet sich mit zwei getrennten Identitäten mit Buluthat:

Einstellung (CRM)Wert
Einstellungen > Auto-Call > API-SchlüsselBei Buluthat erzeugte bt_… Schlüssel (Geltungsbereich: autocall + voicebot + voice_otp + call)
Einstellungen > Auto-Call > SignaturgeheimnisDesselben Schlüssels bts_… Geheimnis. Ist es gesetzt, wird jede Anfrage des CRM an Buluthat signiert
VoIP-Einstellungen > Buluthat API TokenLive-Bildschirm / CDR-Brücke (crm_bridge); wird vom Buluthat-Team vergeben und ist vom obigen Schlüssel getrennt

Empfohlene Reihenfolge: den Schlüssel Signierte Anfrage erforderlich deaktiviert erzeugen, im CRM Schlüssel und Geheimnis eintragen, im Protokoll die Anfragen imzalı mit dem Badge ankommt, machen Sie dann im Schlüssel die Signatur verpflichtend. Fügen Sie dem Schlüssel auch die IP des CRM-Servers hinzu.

Click-to-call (begin_call) sendet den Schlüssel nun im Header statt in der URL. Nach dem CRM-Update können Sie bei alten Schlüsseln die Berechtigung „Schlüssel in der URL“ deaktivieren.

ByCRM-Einrichtung

Im ByCRM jede Firma bei Buluthat mit eigenem Schlüssel angebunden; Firmen können die Daten der jeweils anderen nicht sehen.

  1. Mit dem Konto der Firma im Buluthat-Panel API-Schlüssel > Neuer Schlüssel: Berechtigungen Live-Telefonanlagendaten, Anrufsteuerung und Warteschlangen, Auto-Call (falls verwendet Sprachassistent, Sprach-Bestätigungscode). Tragen Sie die IP des ByCRM-Servers ein.
  2. ByCRM > Integrationen > Buluthat:
FeldWert
Buluthat API Tokenbt_…
Signaturgeheimnisbts_…
Bridge Tokenderselbe bt_… Schlüssel
CRM Bridge URLhttps://api.buluthat.com/api/crm_bridge.php
Autocall URLhttps://api.buluthat.com/api/autocall.php
Buluthat Tenant ID / PBX Server IDWird nicht verwendet; es kommen die Daten des Kontos, zu dem der Schlüssel gehört
  1. Testen Sie den Live-Bildschirm und Click-to-call, im Protokoll imzalı die Badges, machen Sie dann im Schlüssel Signierte Anfrage erforderlichöffnen.
Da der Live-Bildschirm die Brücke alle paar Sekunden abfragt, gilt für Brückenanfragen eine eigene, großzügigere Ratenbegrenzung (1.200 pro Minute und Schlüssel); ins Protokoll werden nur fehlerhafte Brückenanfragen geschrieben.

Webhooks verifizieren

Auch die Webhooks, die Buluthat Ihnen sendet, sind signiert (X-Buluthat-Signature: sha256=…). Prüfen Sie auf Ihrem Server die Signatur Rohbody verarbeiten Sie keinen Webhook ohne Verifizierung über X-Buluthat-Delivery verarbeiten Sie dieselbe Zustellung nicht zweimal. Einzelheiten: Webhooks.

Sicherheits-Checkliste

  • [ ] Jede Integration hat ihren eigenen Schlüssel, nur mit den nötigen Geltungsbereichen
  • [ ] Schlüssel stehen nicht im Code, sondern in Umgebungsvariablen oder einer Geheimnisverwaltung (.env Wird nicht in Git eingecheckt)
  • [ ] Der Schlüssel liegt nur serverseitig; nicht im Browser-JavaScript, nicht in der Mobil-App, nicht in einem Excel-Makro
  • [ ] Die Liste zugelassener IPs ist ausgefüllt
  • [ ] „Signierte Anfrage erforderlich“ ist aktiv
  • [ ] Schlüssel in der URL (?key=) deaktiviert
  • [ ] Es gibt ein Ablaufdatum oder eine Erneuerungserinnerung im Kalender
  • [ ] Der Client prüft das TLS-Zertifikat (CURLOPT_SSL_VERIFYPEER aktiv); bei deaktivierter Verifizierung kann jemand dazwischen die Anfrage lesen und verändern
  • [ ] Die Serverzeit ist per NTP synchronisiert
  • [ ] Die Webhook-Signatur wird verifiziert
  • [ ] Das Protokoll wird monatlich geprüft; Schlüssel, auf die ausgeschiedene Mitarbeiter Zugriff hatten, wurden erneuert

Meldung einer Sicherheitslücke

Wenn Sie glauben, eine Sicherheitslücke in der Buluthat-API gefunden zu haben, melden Sie sich über das Support-Center im Panel „Sicherheitsmitteilung“ Eintrag mit dem Thema eröffnen. Beschreiben Sie, wie Sie die Lücke reproduziert haben, und falls vorhanden X-Request-Id fügen Sie die Werte hinzu. Wir bitten Sie, die Einzelheiten nicht zu teilen, bis wir die Meldung geprüft und behoben haben.