WhatsApp API
Senden Sie Ihre erste Nachricht#
Eine reine Textnachricht. Der Body entspricht der Standard-Cloud-API.
POST/v26.0/{phone-number-id}/messagesAuthorization: Bearer <DEVICE_KEY>
curl -X POST https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages \
-H "Authorization: Bearer YOUR_DEVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"to": "15551234567",
"type": "text",
"text": { "body": "Hello from SyBox" }
}'const res = await fetch('https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.SYBOX_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
messaging_product: 'whatsapp',
to: '15551234567',
type: 'text',
text: { body: 'Hello from SyBox' }
})
});
console.log(res.status, await res.json());import os, requests
r = requests.post(
"https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages",
headers={"Authorization": "Bearer " + os.environ["SYBOX_KEY"]},
json={
"messaging_product": "whatsapp",
"to": "15551234567",
"type": "text",
"text": {"body": "Hello from SyBox"},
},
timeout=30,
)
print(r.status_code, r.json())$ch = curl_init('https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SYBOX_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'messaging_product' => 'whatsapp',
'to' => '15551234567',
'type' => 'text',
'text' => ['body' => 'Hello from SyBox'],
]),
]);
echo curl_exec($ch);using System.Net.Http.Headers;
using System.Text;
using var http = new HttpClient(); // in a real app, keep one HttpClient for the whole app
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SYBOX_KEY"));
string json = """
{
"messaging_product": "whatsapp",
"to": "15551234567",
"type": "text",
"text": { "body": "Hello from SyBox" }
}
""";
var res = await http.PostAsync(
"https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages",
new StringContent(json, Encoding.UTF8, "application/json"));
Console.WriteLine(await res.Content.ReadAsStringAsync());
Die Antwort#
Sie erhalten genau das zurück, was Meta zurückgibt, mit demselben HTTP-Status.
200 OK · application/json {
"messaging_product": "whatsapp",
"contacts": [{ "input": "15551234567", "wa_id": "15551234567" }],
"messages": [{ "id": "wamid.HBgLMTU1NTEyMzQ1NjcVAgARGBI…" }]
}
Ihr System in SyBox
Zeigen Sie Ihre Daten neben dem Chat an#
Wenn ein Mitarbeiter einen Chat öffnet, ruft SyBox IHREN Endpunkt auf und zeigt die Daten direkt neben dem Chat an.
Fügen Sie Ihren Endpunkt unter Einstellungen › Integrationen › ERP APIs hinzu. Jeder Aufruf ist ein POST mit Ihrem Schlüssel in Authorization und der Anfrage in einem einzigen Feld, data, das einen JSON-String enthält – so kann jede Seite später Felder hinzufügen, ohne die andere zu beeinträchtigen. Antworten Sie innerhalb von 15 Sekunden.
POST YOUR_ERP_URL
Authorization: Bearer YOUR_ERP_KEY
X-Sybox-Action: lookup
X-Sybox-Lang: en
X-Sybox-Theme: light
X-Sybox-Agent: 6f1c…
X-Sybox-Number: 104857600000000
Content-Type: application/json
{ "data": "{\"phone\":\"15551234567\",\"email\":\"customer@example.com\",\"ref\":\"\"}" }{
"renderAs": "table",
"content": [
{ "Order": "#10432", "Status": "Shipped", "Total": "$1,250.00", "sybox_ref": "10432" },
{ "Order": "#10388", "Status": "Delivered", "Total": "$310.00", "sybox_ref": "10388" }
]
}
Jeder Aufruf gibt außerdem an, wer fragt: X-Sybox-Lang (die Sprache des Benutzers – antworten Sie in dieser Sprache), X-Sybox-Theme (light oder dark, für eine html-Antwort), X-Sybox-Agent (die ID des Benutzers) und X-Sybox-Number (die Leitung, auf der die Konversation läuft).
Wir senden Telefon und E-Mail — antworten Sie mit { renderAs, content }.
renderAs#
Legen Sie renderAs fest, um SyBox mitzuteilen, wie Ihr Inhalt dargestellt werden soll:
| renderAs | Beschreibung |
|---|
table | Zeilen und Spalten. content ist ein Array flacher Objekte (oder { rows: [ … ] }); ihre Schlüssel werden zu den Spaltenüberschriften. |
cards · kpi | Kleine Kacheln, etwa ein Kontostand oder eine Anzahl von Bestellungen. content ist ein Array von { label, value } (oder { cards: [ … ] }). |
feed · list · thread | Eine Zeitleiste aus Karten. content ist ein Array von { title, text, date, tags, media: [{ type, url, name }] }. |
message | Ein einzelner Datensatz, dargestellt als eine Karte im Stil einer Nachricht. |
html | Ihr eigenes HTML, als String in content. Nur für Markup, das Sie selbst erzeugen. |
sections | Mehrere Blöcke in einer Antwort: content ist { sections: [ { title, renderAs, content } ] }. |
json | Die Standardeinstellung, wenn renderAs fehlt: SyBox zeigt Ihren content als formatiertes JSON an. |
Fügen Sie sybox_ref zu jeder Tabellenzeile hinzu, um sie anklickbar zu machen.
Produktsuche (X-Sybox-Action: products)#
Derselbe Endpunkt kann auch Produktsuchen beantworten. Der Benutzer gibt ein, wonach der Kunde fragt, SyBox sendet dies als "q", und Ihre Antwort wird als Produktkarten neben dem Chat dargestellt – mit einem Ein-Klick-Senden, das den Produktlink in das Eingabefeld einfügt.
POST YOUR_ERP_URL
Authorization: Bearer YOUR_ERP_KEY
X-Sybox-Action: products
# the decoded "data":
{ "q": "wireless keyboard", "phone": "15551234567", "email": "customer@example.com", "ref": "" }{
"content": [
{
"name": "Wireless Keyboard K380",
"price": 449,
"oldPrice": 520,
"currency": "USD",
"available": true,
"stock": 12,
"image": "https://cdn.example.com/k380.jpg",
"link": "https://shop.example.com/p/k380",
"note": "Ships in 24h"
}
]
}
| Feld | Beschreibung |
|---|
nameerforderlich | Das einzige Pflichtfeld. Ein Produkt mit nur einem Namen ergibt trotzdem eine saubere Karte. |
price · oldPrice · currency | Zahlen, keine formatierten Zeichenketten. Senden Sie oldPrice nur, wenn es einen echten früheren Preis gibt — der Rabatt wird aus beiden berechnet. |
available · stock | available ist true/false; stock ist eine optionale Anzahl daneben. Lassen Sie beide weg, wenn Sie keinen Bestand führen — ein fehlendes Feld zeichnet nichts und wird nie als «nicht verfügbar» gelesen. |
image | Eine direkte https-Bild-URL. Was nicht lädt, weicht einem neutralen Hintergrund. |
link | Die URL der Produktseite. Ohne sie ist die Karte nur lesbar — Senden erscheint nur, wenn es etwas zu senden gibt. |
note | Eine kurze freie Zeile unter dem Preis — Lieferzeit, Variante, Bedingung. |
Nichts außer dem Endpunkt, den Sie bereits haben: dieselbe URL, derselbe Schlüssel, dieselbe { "data": "…" }-Hülle. Unterscheiden Sie über X-Sybox-Action zwischen Produktsuche und Kundenabfrage. Antworten Sie mit content als einfachem Array (oder { items: [ … ] }).
Ereignisse
Webhooks: Ereignisse auf Ihrem Server#
Fügen Sie Ihren Endpunkt unter Einstellungen › Integrationen › Webhooks hinzu, wählen Sie die Ereignisse und die Nummern aus, und SyBox sendet jedes Ereignis per POST an ihn, sobald es eintritt. Ein WhatsApp-Ereignis kommt im Webhook-Format von Meta an, sodass ein für Meta geschriebener Handler unverändert funktioniert.
| Ereignis | Beschreibung |
|---|
messageEingehende Nachricht | Eine Nachricht eines Kunden. |
statusNachrichtenstatus | Gesendet, zugestellt, gelesen oder fehlgeschlagen – für eine Nachricht, die Sie gesendet haben. |
reactionReaktion | Ein Kunde hat auf eine Nachricht reagiert. |
doneUnterhaltung als erledigt markiert | Eine Unterhaltung wurde als erledigt markiert: von einer Person, durch eine Automatisierung oder automatisch nach Schweigen des Kunden. |
labelLabel hinzugefügt oder entfernt | Ein Label wurde zu einer Unterhaltung hinzugefügt oder daraus entfernt. |
contactKontakt erstellt oder aktualisiert | Ein Kontakt wurde erstellt oder sein Name, seine E-Mail-Adresse oder seine Kontaktfelder wurden geändert. |
dealDeal abgeschlossen | In einer Unterhaltung wurde ein abgeschlossener Deal erfasst. |
caseCRM-Fall geöffnet oder geschlossen | Ein CRM-Fall wurde geöffnet oder geschlossen (gelöst, nicht gelöst oder abgebrochen). |
Anfrage · SyBox → Sie POST YOUR_WEBHOOK_URL
Content-Type: application/json
X-Hub-Signature-256: sha256=5d1f0c…
x-sybox-signature: sha256=5d1f0c…
x-sybox-event: message
x-sybox-timestamp: 1761400000
{
"object": "whatsapp_business_account",
"entry": [{
"id": "WABA_ID",
"changes": [{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": { "display_phone_number": "15550001111", "phone_number_id": "PHONE_NUMBER_ID" },
"contacts": [{ "profile": { "name": "Sara" }, "wa_id": "15551234567" }],
"messages": [{
"from": "15551234567",
"id": "wamid.HBgLMTU1NTEyMzQ1NjcVAgASGBQ…",
"timestamp": "1761400000",
"type": "text",
"text": { "body": "Is my order shipped?" }
}]
}
}]
}]
}
Medien. Der Link von Meta lässt sich nur mit einem Meta-Token öffnen. Deshalb ersetzt SyBox ihn durch einen signierten Link zu seiner eigenen Kopie der Datei, gültig für 24 Stunden. Eine Sprachnachricht kann außerdem ihren Text in sybox.transcription mitliefern, wenn der Webhook ihn anfordert.
Andere Kanäle – Messenger, Instagram, Telegram und Live-Chat – kommen im eigenen Format von SyBox an, nur für das Nachrichtenereignis:
{
"object": "sybox",
"event": "message",
"channel": "telegram",
"line": "8123456789",
"workspace": "yourworkspace",
"contact": { "key": "tg:6423115118", "name": "Sara" },
"message": {
"id": "812345",
"timestamp": 1761400000,
"type": "image",
"text": "",
"caption": "The broken part",
"media": { "url": "https://api.sybox.io/…", "mime": "image/jpeg" }
}
}
Unterhaltungsereignisse kommen für jeden Kanal im eigenen Format von SyBox an. event gibt genau an, was passiert ist:
| Ereignis | <code>event</code> |
|---|
| Unterhaltung als erledigt markiert | conversation.done |
| Label hinzugefügt oder entfernt | label.added · label.removed |
| Kontakt erstellt oder aktualisiert | contact.created · contact.updated |
| Deal abgeschlossen | deal.closed |
| CRM-Fall geöffnet oder geschlossen | case.opened · case.closed |
| Testereignis | test |
{
"object": "sybox",
"event": "conversation.done",
"channel": "whatsapp",
"line": "201001234567",
"workspace": "yourworkspace",
"timestamp": 1761400000,
"contact": {
"key": "201112223344",
"name": "Sara",
"phone": "201112223344",
"email": "",
"channel": "whatsapp",
"labels": [{ "id": 3, "name": "VIP" }],
"fields": [{ "id": 2, "key": "city", "name": "City", "value": "Cairo" }]
},
"data": { "by": "Omar", "note": "Refund sent" }
}{
"object": "sybox",
"event": "case.closed",
"workspace": "yourworkspace",
"timestamp": 1761400000,
"case": {
"id": 8124,
"guid": "5f0c1e9a…",
"subject": "Screen replacement not delivered",
"type": 1,
"status": "solved",
"department": "Support"
},
"customer": { "name": "Sara", "phone": "201112223344" }
}
Signaturschlüssel#
| Header | Beschreibung |
|---|
X-Hub-Signature-256 | sha256= und der hexadezimale HMAC-SHA256 des unveränderten Bodys, berechnet mit dem Signatur-Secret des Webhooks – derselbe Header und dieselbe Methode wie bei Meta. |
x-sybox-signature | Derselbe Wert, damit ein Endpunkt, der auch Aufrufe direkt von Meta empfängt, unsere davon unterscheiden kann. |
x-sybox-event | Was passiert ist: message, status, reaction, eines der unten aufgeführten Unterhaltungsereignisse (conversation.done, label.added…) oder test. |
x-sybox-timestamp | Wann es gesendet wurde, in Unix-Sekunden. |
x-sybox-signature-rsa | Eine zweite Signatur: rsa-sha256= und die Base64-codierte RSA-SHA256-Signatur (PKCS#1 v1.5) von x-sybox-timestamp, einem Punkt und dem unveränderten Body. |
x-sybox-key-id | Welcher unserer Schlüssel sie erstellt hat. Der öffentliche Schlüssel liegt unter https://api.sybox.io/.well-known/sybox-webhook-key. |
Prüfen Sie die Signatur anhand der unveränderten Bytes, bevor Sie sie parsen, und vergleichen Sie in konstanter Zeit:
const crypto = require('crypto');
// express.raw: the signature is over the bytes as sent, so read them before any JSON parser.
app.post('/sybox/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sent = Buffer.from(req.get('X-Hub-Signature-256') || '');
const want = Buffer.from('sha256=' + crypto
.createHmac('sha256', process.env.SYBOX_WEBHOOK_SECRET)
.update(req.body)
.digest('hex'));
if (sent.length !== want.length || !crypto.timingSafeEqual(sent, want)) return res.sendStatus(401);
res.sendStatus(200); // answer first, then do the work
const event = JSON.parse(req.body);
const kind = req.get('x-sybox-event'); // message | status | reaction
});$raw = file_get_contents('php://input');
$want = 'sha256=' . hash_hmac('sha256', $raw, getenv('SYBOX_WEBHOOK_SECRET'));
$sent = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
if (!hash_equals($want, $sent)) {
http_response_code(401);
exit;
}
http_response_code(200);
$event = json_decode($raw, true);
$kind = $_SERVER['HTTP_X_SYBOX_EVENT'] ?? ''; // message | status | reactionimport hashlib, hmac, os
from flask import Flask, abort, request
app = Flask(__name__)
@app.post("/sybox/webhook")
def sybox_webhook():
raw = request.get_data()
want = "sha256=" + hmac.new(os.environ["SYBOX_WEBHOOK_SECRET"].encode(), raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(want, request.headers.get("X-Hub-Signature-256", "")):
abort(401)
event = request.get_json()
kind = request.headers.get("x-sybox-event") # message | status | reaction
return "", 200using System.Security.Cryptography;
using System.Text;
app.MapPost("/sybox/webhook", async (HttpRequest req) =>
{
using var ms = new MemoryStream();
await req.Body.CopyToAsync(ms);
byte[] raw = ms.ToArray();
string secret = Environment.GetEnvironmentVariable("SYBOX_WEBHOOK_SECRET")!;
byte[] mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), raw);
byte[] want = Encoding.ASCII.GetBytes("sha256=" + Convert.ToHexString(mac).ToLowerInvariant());
byte[] sent = Encoding.ASCII.GetBytes(req.Headers["X-Hub-Signature-256"].ToString());
if (!CryptographicOperations.FixedTimeEquals(want, sent)) return Results.Unauthorized();
string kind = req.Headers["x-sybox-event"]; // message | status | reaction
return Results.Ok();
});
Sie möchten kein gemeinsames Secret auf Ihrem Server speichern? Prüfen Sie stattdessen x-sybox-signature-rsa mit unserem öffentlichen Schlüssel. Lehnen Sie in beiden Fällen einen Zeitstempel ab, der älter als fünf Minuten ist.
Antworten Sie innerhalb von 15 Sekunden mit einem beliebigen 2xx-Code. Alles andere – ein Fehler, eine Zeitüberschreitung, eine Weiterleitung – wird nach 1, 2, 4, 8, 16 und 32 Minuten erneut versucht und dann verworfen. Ein Ereignis kann mehr als einmal ankommen: Verwenden Sie die ID der Nachricht, um eine Wiederholung zu ignorieren. Die Adresse muss öffentlich sein und https verwenden.
Ein Webhook, der über mehr als eine Stunde 20-mal in Folge fehlschlägt, wird automatisch ausgeschaltet und der Workspace-Administrator wird benachrichtigt. Seine wartenden Ereignisse werden 24 Stunden lang aufbewahrt: Schalten Sie ihn wieder ein, um sie zu senden.
Klicken Sie zum Testen auf der Karte des Webhooks auf Testereignis senden. Unter Zustellungen auf derselben Karte werden die letzten 50 Zustellungen samt der Antwort Ihres Servers aufgelistet, und Sie können eine Zustellung erneut senden.
Live-Chat
Live-Chat#
Binden Sie einen Chat in Ihre Website ein: Besucher schreiben dort, Ihr Team antwortet in SyBox. Fügen Sie die Website unter Einstellungen › Messaging › Kanäle auf der Karte Live-Chat hinzu; das Fenster, das sich öffnet, Code für Ihre Website, enthält den Code und die unten beschriebenen Schlüssel. Die drei geheimen Schlüssel werden nur einmal angezeigt.
| Schlüssel | Beginnt mit | Verwendungszweck |
|---|
| Öffentlicher Schlüssel | lcpk_ | Kommt in Ihre Seiten. Kann bedenkenlos veröffentlicht werden: Er benennt nur die Website, und der Chat erscheint nur auf Ihren Website-Adressen. |
| Signaturschlüssel für angemeldete Benutzer | lcid_ | Nur auf Ihrem Server. Signiert jeden angemeldeten Benutzer, damit niemand im Namen eines anderen schreiben kann. |
| Serverschlüssel (für Ihren eigenen Chat-Bildschirm) | lcsk_ | Nur auf Ihrem Server. Nur für eine Website, die statt des fertigen Chats einen eigenen Chat-Bildschirm baut. |
| Signaturschlüssel für Antworten | lcwh_ | Nur auf Ihrem Server. Belegt, dass eine an Ihren Server gesendete Antwort von SyBox stammt. |
Der fertige Chat braucht nur den Code. Der Signaturschlüssel ist für eine Website mit Benutzerkonten; der Serverschlüssel und der Signaturschlüssel für Antworten nur für eine Website mit eigenem Chat-Bildschirm.
Wenn Sie eine Website ausschalten, wird der Chat sofort ausgeblendet. Die Aufrufe Ihres Servers werden weiterhin angenommen und gespeichert.
Den Chat in Ihre Website einbinden#
Fügen Sie den Code einmal in jede Seite ein, direkt vor dem schließenden body-Tag. Der Chat erscheint nur auf den Adressen unter Website-Adressen, und Farbe, Position, Titel und Begrüßung legen Sie in SyBox fest, nicht im Code.
<script async src="https://api.sybox.io/lc/w.js" data-key="lcpk_YOUR_PUBLIC_KEY"></script>
WordPress: Laden Sie unser Plugin im selben Fenster herunter. Shopify: Aktivieren Sie in der Shopify-App von SyBox Live-Chat in diesem Shop und danach SyBox Live Chat unter App-Einbettungen – ein angemeldeter Kunde wird erkannt, ohne dass Sie etwas weiter tun müssen.
Angemeldete Benutzer: der Signaturschlüssel#
Wenn ein Benutzer auf Ihrer Website angemeldet ist, teilt Ihr Server dem Chat mit einem kurzen Token – einem JWT, HS256 –, das mit dem Signaturschlüssel signiert ist, mit, wer er ist. Der Benutzer hat dann eine Konversation unter seinem Namen, auf jedem Gerät, und niemand kann sie öffnen, indem er die ID eines anderen eingibt.
- Nach der Anmeldung erstellt Ihr Server das Token aus den unten aufgeführten Claims.
- Die Seite übergibt es dem Chat als
identity, vor dem Code des Chats. - Nach einer Anmeldung ohne Neuladen der Seite rufen Sie stattdessen
SyBoxChat('identify', token) auf.
| Feld | Beschreibung |
|---|
suberforderlichstring | Die ID Ihres Benutzers, bis zu 120 Zeichen. Sie darf nicht mit v_ beginnen. |
experforderlichnumber | Ablaufzeit in Unix-Sekunden – höchstens einen Tag im Voraus. Zehn Minuten sind passend: Das Token wird nur beim Öffnen des Chats gelesen. |
namestring | Der Name, den die Konversation anzeigt. |
emailstring | Die E-Mail-Adresse des Benutzers. |
phonestring | Die Telefonnummer des Benutzers. |
picturestring | Eine https-Adresse des Fotos des Benutzers, bis zu 500 Zeichen. SyBox speichert eine eigene Kopie und lädt das Foto bei der nächsten Anmeldung erneut, wenn sich die Adresse geändert hat. |
function b64url(string $s): string { return rtrim(strtr(base64_encode($s), '+/', '-_'), '='); }
$header = b64url(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
$payload = b64url(json_encode([
'sub' => (string) $user->id,
'name' => $user->name,
'email' => $user->email,
'exp' => time() + 600, // ten minutes
]));
$signature = b64url(hash_hmac('sha256', "$header.$payload", getenv('SYBOX_LC_SIGNING_KEY'), true));
$token = "$header.$payload.$signature";const crypto = require('crypto');
const b64url = (s) => Buffer.from(s).toString('base64url');
const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
const payload = b64url(JSON.stringify({
sub: String(user.id),
name: user.name,
email: user.email,
exp: Math.floor(Date.now() / 1000) + 600 // ten minutes
}));
const signature = crypto.createHmac('sha256', process.env.SYBOX_LC_SIGNING_KEY)
.update(header + '.' + payload).digest('base64url');
const token = header + '.' + payload + '.' + signature;import base64, hashlib, hmac, json, os, time
def b64url(b: bytes) -> str:
return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
header = b64url(json.dumps({"alg": "HS256", "typ": "JWT"}).encode())
payload = b64url(json.dumps({
"sub": str(user.id),
"name": user.name,
"email": user.email,
"exp": int(time.time()) + 600, # ten minutes
}).encode())
key = os.environ["SYBOX_LC_SIGNING_KEY"].encode()
signature = b64url(hmac.new(key, f"{header}.{payload}".encode(), hashlib.sha256).digest())
token = f"{header}.{payload}.{signature}"using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
static string B64Url(byte[] b) => Convert.ToBase64String(b).TrimEnd('=').Replace('+', '-').Replace('/', '_');
string header = B64Url(JsonSerializer.SerializeToUtf8Bytes(new { alg = "HS256", typ = "JWT" }));
string payload = B64Url(JsonSerializer.SerializeToUtf8Bytes(new {
sub = user.Id.ToString(),
name = user.Name,
email = user.Email,
exp = DateTimeOffset.UtcNow.AddMinutes(10).ToUnixTimeSeconds()
}));
byte[] key = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SYBOX_LC_SIGNING_KEY"));
string signature = B64Url(HMACSHA256.HashData(key, Encoding.ASCII.GetBytes(header + "." + payload)));
string token = header + "." + payload + "." + signature;
<script>
window.SyBoxChatConfig = { identity: "TOKEN_FROM_YOUR_SERVER", locale: "en" };
</script>
<script async src="https://api.sybox.io/lc/w.js" data-key="lcpk_YOUR_PUBLIC_KEY"></script>
Erstellen Sie das Token nur auf Ihrem Server. Ein Signaturschlüssel in einer Webseite ermöglicht es jedem, im Namen jedes Ihrer Benutzer zu schreiben. Und setzen Sie nie eine bloße Benutzer-ID anstelle des Tokens in die Seite: Damit könnte jeder wählen, wessen Konversation er liest.
Wenn Nur angemeldete Benutzer meiner Website aktiviert ist, sieht ein Besucher ohne Token den Chat nicht.
Ein Besucher, der vor der Anmeldung gechattet hat, wird einmal im selben Browser gefragt, ob er diese Konversation mit seinem Konto fortsetzen möchte. Dafür müssen Sie nichts tun.
Den Chat von Ihrer Seite aus steuern#
Der Code des Chats fügt Ihrer Seite eine Funktion hinzu, SyBoxChat:
SyBoxChat('open'); // open the chat
SyBoxChat('close'); // close it
SyBoxChat('identify', token); // after a sign-in without a page reload
SyBoxChat('locale', 'fr'); // the chat's language
SyBoxChat('on', 'message', function (m) {
// a reply arrived
});
SyBoxChat('on', 'open', function () { /* the chat opened */ });
SyBoxChat('on', 'close', function () { /* the chat closed */ });
// Before the chat's code, every setting is optional:
window.SyBoxChatConfig = {
identity: 'TOKEN_FROM_YOUR_SERVER', // a signed-in user (see above)
name: 'Sara', email: 'sara@example.com', // a visitor without an account: fills the form for them
locale: 'ar',
serviceWorker: 'auto' // 'auto' | '/your-sw.js' | 'site' | 'off'
};
Benachrichtigungen, nachdem Ihre Website geschlossen wurde#
Solange eine Seite Ihrer Website geöffnet ist, erscheint eine Antwort von selbst als Benachrichtigung. Um einen Besucher zu erreichen, nachdem Ihre Website geschlossen wurde, verlangen Browser einen Service Worker auf Ihrer eigenen Domain: Stellen Sie diese eine Zeile als /sybox-lc-sw.js im Stammverzeichnis Ihrer Website bereit, als application/javascript.
try { importScripts('https://api.sybox.io/lc/sw.js'); } catch (e) {}
Wählen Sie den Modus in SyBox (Benachrichtigungen bei geschlossener Website) oder in der Seite mit serviceWorker: "auto" (die Datei oben, die Standardeinstellung), ein eigener Pfad, "site" (dieselbe Zeile ganz oben in Ihren eigenen Service Worker setzen) oder "off".
Der Chat ersetzt nie einen Service Worker, den Ihre Website bereits hat, und die Datei hat keinen Fetch-Handler, sieht also nie eine Anfrage Ihrer Website. Das WordPress-Plugin stellt die Datei für Sie bereit. Safari auf dem iPhone zeigt für eine normale Website keine Benachrichtigungen an.
Ihr eigener Chat-Bildschirm: der Serverschlüssel#
Eine Website oder App mit eigenem Chat-Bildschirm kommuniziert von ihrem Server aus mit SyBox, statt den fertigen Chat zu verwenden. Jeder Aufruf enthält Authorization: Bearer lcsk_…. Ihr Server benennt den Benutzer mit Ihrer eigenen ID, und ihm wird dabei vertraut, weil er den Schlüssel besitzt.
| Aufruf | Beschreibung |
|---|
POST /v1/livechat/messages | Ihr Benutzer schreibt. client_id macht einen erneuten Versuch sicher: Dieselbe ID wird nur einmal gespeichert ("duplicate": 1). |
POST /v1/livechat/upload | Eine Datei, als Multipart-Formulardaten: file, user_id, client_id. Bis zu 16 MB: Bilder, Sprachnachrichten, Videos, PDF, Text und Office-Dokumente. |
GET /v1/livechat/messages?user_id=&since=&before=&limit= | Die Konversation: die Nachrichten nach since und agents_read_id, die neueste Nachricht Ihres Benutzers, die ein Agent gelesen hat. |
POST /v1/livechat/read | Ihr Benutzer hat die Antworten bis up_to gelesen. |
POST /v1/livechat/edit | Ihr Benutzer bearbeitet seine eigene Nachricht, innerhalb von 15 Minuten. |
POST /v1/livechat/delete | Ihr Benutzer löscht seine eigene Nachricht für alle, innerhalb von 24 Stunden. |
POST /v1/livechat/react | Eine Reaktion auf eine beliebige Nachricht; ein leeres emoji entfernt sie. |
POST /v1/livechat/typing | Ihr Benutzer tippt: Senden Sie alle paar Sekunden on: 1 und on: 0, wenn er aufhört. |
curl -X POST https://api.sybox.io/v1/livechat/messages \
-H "Authorization: Bearer $SYBOX_LC_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"user": { "id": "u42", "name": "Sara Ali", "email": "sara@example.com", "locale": "ar" },
"message": { "client_id": "a1b2c3", "text": "Hi, is my order on its way?", "reply_to": "" }
}'{ "ok": 1, "id": 981, "guid": "7c9e…", "duplicate": 0 }{ "user_id": "u42", "up_to": 990 } // read
{ "user_id": "u42", "id": 981, "text": "…" } // edit
{ "user_id": "u42", "id": 981 } // delete
{ "user_id": "u42", "id": 990, "emoji": "👍" } // react
{ "user_id": "u42", "on": 1 } // typing
Eine Nachricht, im Verlauf und in Antworten:
{
"id": 990,
"guid": "b81d…",
"dir": "out", // "in" = your user wrote it, "out" = your team
"type": "image", // text | image | audio | video | file
"text": "Here is the tracking page",
"media": { "url": "https://api.sybox.io/…", "mime": "image/png", "name": "tracking.png", "w": 1080, "h": 720 },
// a voice note or a video also has "secs"
"status": 3, // on your user's messages: 1 received, 3 read by an agent
"reply_to": "7c9e…",
"client": "a1b2c3", // on your user's messages: the client_id you sent
"react_us": "", // your team's reaction on it
"react_me": "👍", // your user's reaction on it
"at": "2026-10-25T09:41:07Z",
"edited": 0
}
Der Verlauf eines Benutzers, der noch nie geschrieben hat, antwortet mit 404 unknown_user: Zeigen Sie eine leere Konversation an.
Medienlinks sind signiert und eine Stunde lang gültig; fordern Sie danach einen neuen an. SyBox lädt nie eine Datei von einer Adresse, die Sie senden: Laden Sie sie hoch. Bis zu 120 Aufrufe pro Minute und Website.
Antworten auf Ihrem Server: der Signaturschlüssel für Antworten#
Tragen Sie bei der Website die Serveradresse für Antworten ein. Wenn Ihr Team antwortet, bearbeitet, löscht oder liest, sendet SyBox das Ereignis per POST dorthin, signiert mit dem Signaturschlüssel für Antworten.
| Header | Beschreibung |
|---|
x-sybox-signature | t=TIMESTAMP,v1=SIGNATURE – TIMESTAMP in Unix-Sekunden, SIGNATURE der hexadezimale HMAC-SHA256 von TIMESTAMP.BODY (dem unveränderten Body), berechnet mit dem Signaturschlüssel für Antworten. |
x-sybox-event | message.created, message.updated, message.deleted oder messages.read. |
x-sybox-delivery | Bei jedem erneuten Versuch desselben Ereignisses gleich: Verwenden Sie ihn, um eine Wiederholung zu ignorieren. |
Der Body enthält event, line_id, user_id und message – oder message_id bei einer Löschung und read_id bei messages.read: Die Nachrichten Ihres Benutzers sind bis zu dieser ID gelesen.
const crypto = require('crypto');
app.post('/sybox/livechat', express.raw({ type: 'application/json' }), (req, res) => {
const parts = Object.fromEntries((req.get('x-sybox-signature') || '').split(',').map(p => p.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(401); // older than five minutes
const want = Buffer.from(crypto.createHmac('sha256', process.env.SYBOX_LC_REPLY_KEY)
.update(t + '.').update(req.body).digest('hex'));
const sent = Buffer.from(parts.v1 || '');
if (sent.length !== want.length || !crypto.timingSafeEqual(sent, want)) return res.sendStatus(401);
res.sendStatus(200);
const delivery = req.get('x-sybox-delivery'); // seen it already? ignore
const evt = JSON.parse(req.body); // { event, line_id, user_id, message | message_id | read_id }
});$raw = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_SYBOX_SIGNATURE'] ?? ''), $sig);
$t = (int) ($sig['t'] ?? 0);
$want = hash_hmac('sha256', $t . '.' . $raw, getenv('SYBOX_LC_REPLY_KEY'));
if (abs(time() - $t) > 300 || !hash_equals($want, $sig['v1'] ?? '')) {
http_response_code(401);
exit;
}
http_response_code(200);
$delivery = $_SERVER['HTTP_X_SYBOX_DELIVERY'] ?? ''; // seen it already? ignore
$evt = json_decode($raw, true);import hashlib, hmac, os, time
from flask import abort, request
@app.post("/sybox/livechat")
def sybox_livechat():
raw = request.get_data()
sig = dict(p.split("=", 1) for p in request.headers.get("x-sybox-signature", "").split(",") if "=" in p)
t = int(sig["t"]) if sig.get("t", "").isdigit() else 0 # a malformed header is a 401, not a crash
want = hmac.new(os.environ["SYBOX_LC_REPLY_KEY"].encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
if abs(time.time() - t) > 300 or not hmac.compare_digest(want, sig.get("v1", "")):
abort(401)
delivery = request.headers.get("x-sybox-delivery") # seen it already? ignore
evt = request.get_json()
return "", 200using System.Security.Cryptography;
using System.Text;
app.MapPost("/sybox/livechat", async (HttpRequest req) =>
{
using var ms = new MemoryStream();
await req.Body.CopyToAsync(ms);
byte[] raw = ms.ToArray();
var sig = new Dictionary<string, string>();
foreach (var part in req.Headers["x-sybox-signature"].ToString().Split(','))
{
var kv = part.Split('=', 2);
if (kv.Length == 2) sig[kv[0].Trim()] = kv[1].Trim(); // a malformed header is a 401, not a crash
}
if (!long.TryParse(sig.GetValueOrDefault("t"), out long t) ||
Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return Results.Unauthorized();
byte[] signedBytes = Encoding.ASCII.GetBytes(t + ".").Concat(raw).ToArray();
string replyKey = Environment.GetEnvironmentVariable("SYBOX_LC_REPLY_KEY")!;
byte[] mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(replyKey), signedBytes);
byte[] want = Encoding.ASCII.GetBytes(Convert.ToHexString(mac).ToLowerInvariant());
byte[] sent = Encoding.ASCII.GetBytes(sig.GetValueOrDefault("v1", ""));
if (!CryptographicOperations.FixedTimeEquals(want, sent)) return Results.Unauthorized();
return Results.Ok();
});
Prüfen Sie die Signatur anhand des unveränderten Bodys, lehnen Sie ein t ab, das älter als fünf Minuten ist, und antworten Sie schnell mit 2xx. Alles andere wird nach 10 s, 30 s, 2 min, 10 min und 30 min erneut versucht und dann verworfen. SyBox ruft nur öffentliche Adressen auf und folgt nie einer Weiterleitung.
Fehlercodes#
Jede Antwort des Live-Chats ist JSON – {"ok": 1, …} oder {"ok": 0, "error": "CODE"} mit einem Code statt eines Satzes:
| Code | Status | Beschreibung |
|---|
unauthorized | 401 | Der Serverschlüssel ist falsch oder wurde ersetzt. |
invalid_json invalid_payload invalid_user_id | 400 | Die Anfrage hat nicht das erwartete Format. |
empty_message no_file empty_file | 400 | Kein Text und keine Datei. |
too_long | 400 | Der bearbeitete Text ist länger als 4.096 Zeichen. |
not_an_emoji | 400 | emoji ist kein Emoji. |
not_found | 404 | Diese Nachricht gibt es nicht, oder sie gehört nicht zu diesem Benutzer. |
too_late | 409 | Die Zeit zum Bearbeiten oder Löschen ist abgelaufen. |
file_type_not_allowed | 400 | Dieser Dateityp wird nicht akzeptiert. |
file_too_large | 413 | Die Datei ist größer als 16 MB. |
blocked | 403 | Ihr Team hat diesen Benutzer blockiert. |
unknown_user | 404 | Für diese Benutzer-ID gibt es noch keine Konversation. |
rate_limited | 429 | Zu viele Aufrufe: Verlangsamen Sie und versuchen Sie es erneut. |
maintenance | 503 | SyBox wird gerade aktualisiert: Versuchen Sie es nach Retry-After Sekunden erneut. |
server_error | 500 | Auf unserer Seite ist ein Fehler aufgetreten. |
Einen Schlüssel ersetzen#
Einen Schlüssel verloren oder vermuten Sie, dass er öffentlich geworden ist? Klicken Sie auf der Karte der Website auf Neuer Signaturschlüssel, Neuer Serverschlüssel oder Neuer Signaturschlüssel für Antworten. Bei einem neuen Serverschlüssel funktioniert der alte noch einen Tag lang, sodass Sie ohne Unterbrechung umstellen können; die anderen Schlüssel werden sofort ungültig.