WhatsApp API
İlk mesajınızı gönderin#
Düz metin mesajı. Gövde standart Cloud API verisidir.
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());
Yanıt#
Meta'nın döndürdüğü yanıtı aynı HTTP durumuyla birebir alırsınız.
200 OK · application/json {
"messaging_product": "whatsapp",
"contacts": [{ "input": "15551234567", "wa_id": "15551234567" }],
"messages": [{ "id": "wamid.HBgLMTU1NTEyMzQ1NjcVAgARGBI…" }]
}
Sisteminiz SyBox'ta
Verilerinizi sohbetin yanında gösterin#
Bir temsilci sohbeti açtığında SyBox SİZİN uç noktanızı çağırır ve verilerinizi sohbetin yanında gösterir.
Uç noktanızı Ayarlar › Entegrasyonlar › ERP API'leri bölümünde ekleyin. Her çağrı bir POST isteğidir; anahtarınız Authorization içinde, istek ise JSON dizesi taşıyan tek bir alanda, data içinde bulunur — böylece iki taraf da diğerini bozmadan ileride alan ekleyebilir. 15 saniye içinde yanıt verin.
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" }
]
}
Her çağrı ayrıca kimin sorduğunu da bildirir: X-Sybox-Lang (kullanıcının dili — yanıtı bu dilde verin), X-Sybox-Theme (light veya dark, bir html yanıtı için), X-Sybox-Agent (kullanıcının id'si) ve X-Sybox-Number (sohbetin bulunduğu hat).
Telefon ve e-posta gönderiyoruz — { renderAs, content } ile yanıt verin.
renderAs#
SyBox'a içeriği nasıl görüntüleyeceğini bildirmek için renderAs belirleyin:
| renderAs | Açıklama |
|---|
table | Satırlar ve sütunlar. content düz nesnelerden oluşan bir dizidir (veya { rows: [ … ] }); bu nesnelerin anahtarları sütun başlıkları olur. |
cards · kpi | Bakiye veya sipariş sayısı gibi küçük kutucuklar. content, { label, value } öğelerinden oluşan bir dizidir (veya { cards: [ … ] }). |
feed · list · thread | Kartlardan oluşan bir zaman akışı. content, { title, text, date, tags, media: [{ type, url, name }] } öğelerinden oluşan bir dizidir. |
message | Tek bir mesaj kartı biçiminde düzenlenmiş tek bir kayıt. |
html | Kendi HTML'iniz, content içinde bir dize olarak. Yalnızca kendi oluşturduğunuz işaretleme için. |
sections | Tek yanıtta birden fazla blok: content, { sections: [ { title, renderAs, content } ] } biçimindedir. |
json | renderAs belirtilmediğinde varsayılan budur: SyBox content değerinizi biçimlendirilmiş JSON olarak gösterir. |
Herhangi bir tablo satırına sybox_ref ekleyerek tıklanabilir yapın.
Ürün arama (X-Sybox-Action: products)#
Aynı uç nokta ürün aramalarını da yanıtlayabilir. Kullanıcı müşterinin istediği ürünü yazar, SyBox bunu "q" olarak gönderir ve yanıtınız sohbetin yanında ürün kartları olarak gösterilir — tek dokunuşla Gönder özelliği sayesinde ürün bağlantısını mesaj alanına ekler.
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"
}
]
}
| Alan | Açıklama |
|---|
namezorunlu | Tek zorunlu alan. Yalnızca adı olan bir ürün de derli toplu bir kart olur. |
price · oldPrice · currency | Biçimlendirilmiş metin değil, sayı gönderin. oldPrice'ı yalnızca gerçek bir önceki fiyat varsa gönderin — indirim ikisinden hesaplanır. |
available · stock | available true/false'tur; stock yanında gösterilen isteğe bağlı bir sayıdır. Stok takibi yapmıyorsanız ikisini de yazmayın — olmayan alan hiçbir şey çizmez ve asla «tükendi» diye okunmaz. |
image | Doğrudan bir https görsel adresi. Yüklenemeyen görsel yerine nötr bir zemin gösterilir. |
link | Ürün sayfasının adresi. Olmadığında kart yalnızca görüntülenir — Gönder yalnızca gönderilecek bir şey varsa görünür. |
note | Fiyatın altında kısa ve serbest tek satır — teslim süresi, bir varyant, bir koşul. |
Zaten kurduğunuz uç noktadan başkası değil: aynı URL, aynı anahtar, aynı { "data": "…" } zarfı. Ürün aramasını müşteri sorgusundan ayırmak için X-Sybox-Action başlığına bakın. content'i düz bir dizi olarak döndürün (veya { items: [ … ] }).
Olaylar
Webhook'lar: olaylar sunucunuzda#
Uç noktanızı Ayarlar › Entegrasyonlar › Web kancaları bölümünde ekleyin, olayları ve numaraları seçin; SyBox her olayı gerçekleştiği anda ona POST eder. Bir WhatsApp olayı Meta'nın kendi webhook biçiminde gelir; bu nedenle Meta için yazılmış bir işleyici değişiklik yapmadan çalışır.
| Olay | Açıklama |
|---|
messageGelen mesaj | Bir müşterinin mesajı. |
statusMesaj durumu | Gönderildi, teslim edildi, okundu veya başarısız oldu — gönderdiğiniz bir mesaj için. |
reactionTepki | Bir müşteri bir mesaja tepki verdi. |
doneGörüşme tamamlandı olarak işaretlendi | Bir görüşme tamamlandı olarak işaretlendi: bir kişi tarafından, bir otomasyon tarafından veya müşterinin sessizliğinden sonra kendiliğinden. |
labelEtiket eklendi veya kaldırıldı | Bir görüşmeye etiket eklendi veya görüşmeden kaldırıldı. |
contactKişi oluşturuldu veya güncellendi | Bir kişi oluşturuldu ya da adı, e-postası veya kişi alanları değişti. |
dealAnlaşma kapatıldı | Bir görüşmede kapatılan bir anlaşma kaydedildi. |
caseCRM kaydı açıldı veya kapatıldı | Bir CRM kaydı açıldı veya kapatıldı (çözüldü, çözülmedi veya iptal edildi). |
İstek · SyBox → siz 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?" }
}]
}
}]
}]
}
Medya. Meta'nın kendi bağlantısını açmak için bir Meta belirteci gerekir; bu nedenle SyBox onu, dosyanın kendi kopyasına giden ve 24 saat geçerli olan imzalı bir bağlantıyla değiştirir. Webhook istediğinde, bir sesli mesaj metnini sybox.transcription içinde de taşıyabilir.
Diğer kanallar — Messenger, Instagram, Telegram ve Canlı Sohbet — yalnızca mesaj olayı için ve SyBox'un kendi biçiminde gelir:
{
"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" }
}
}
Görüşme olayları her kanal için SyBox'ın kendi biçiminde gelir. event tam olarak ne olduğunu belirtir:
| Olay | <code>event</code> |
|---|
| Görüşme tamamlandı olarak işaretlendi | conversation.done |
| Etiket eklendi veya kaldırıldı | label.added · label.removed |
| Kişi oluşturuldu veya güncellendi | contact.created · contact.updated |
| Anlaşma kapatıldı | deal.closed |
| CRM kaydı açıldı veya kapatıldı | case.opened · case.closed |
| Test olayı | 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" }
}
İmzalama anahtarı#
| Başlık | Açıklama |
|---|
X-Hub-Signature-256 | sha256= ve ardından ham gövdenin, webhook'un imzalama sırrıyla anahtarlanmış onaltılık HMAC-SHA256 değeri — Meta'nınkiyle aynı başlık ve yöntem. |
x-sybox-signature | Aynı değer; böylece Meta'nın kendi çağrılarını da alan bir uç nokta bizimkileri ayırt edebilir. |
x-sybox-event | Ne oldu: message, status, reaction, aşağıdaki görüşme olaylarından biri (conversation.done, label.added…) veya test. |
x-sybox-timestamp | Gönderildiği zaman, Unix saniyesi olarak. |
x-sybox-signature-rsa | İkinci bir imza: rsa-sha256= ve ardından x-sybox-timestamp, bir nokta ve ham gövdenin base64 biçiminde RSA-SHA256 (PKCS#1 v1.5) değeri. |
x-sybox-key-id | İmzayı anahtarlarımızdan hangisinin oluşturduğu. Açık anahtar https://api.sybox.io/.well-known/sybox-webhook-key adresindedir. |
İmzayı ham baytlar üzerinde, ayrıştırmadan önce kontrol edin ve sabit sürede karşılaştırın:
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();
});
Sunucunuzda paylaşılan bir sır tutmak istemiyor musunuz? Bunun yerine x-sybox-signature-rsa değerini açık anahtarımızla kontrol edin. Her iki durumda da beş dakikadan eski bir zaman damgasını reddedin.
15 saniye içinde herhangi bir 2xx ile yanıt verin. Bunun dışındaki her şey — bir hata, zaman aşımı, yönlendirme — 1, 2, 4, 8, 16 ve 32 dakika sonra yeniden denenir, ardından bırakılır. Bir olay birden fazla kez gelebilir: tekrarı yok saymak için mesajın id'sini kullanın. Adres herkese açık ve https olmalıdır.
Bir saatten uzun süre boyunca arka arkaya 20 kez başarısız olan bir webhook kendiliğinden kapatılır ve çalışma alanı yöneticisine haber verilir. Bekleyen olayları 24 saat boyunca saklanır: göndermek için tekrar açın.
Test etmek için webhook kartındaki Test olayı gönder düğmesine basın. Aynı karttaki Teslimatlar, sunucunuzun yanıtıyla birlikte son 50 teslimatı listeler ve birini tekrar gönderebilir.
Canlı Sohbet
Canlı Sohbet#
Web sitenize bir sohbet ekleyin: ziyaretçiler orada yazar, ekibiniz SyBox'ta yanıtlar. Web sitesini Ayarlar › Mesajlaşma › Kanallar bölümünde, Canlı Sohbet kartından ekleyin; açılan pencere, Siteniz için kod, size kodu ve aşağıdaki anahtarları verir. Üç gizli anahtar yalnızca bir kez gösterilir.
| Anahtar | Başlangıcı | Ne işe yarar |
|---|
| Açık anahtar | lcpk_ | Sayfalarınıza konur. Yayımlamak güvenlidir: yalnızca web sitesini tanımlar ve sohbet yalnızca Web sitesi adresleri listenizdeki adreslerde görünür. |
| Giriş yapmış kullanıcılar için imzalama anahtarı | lcid_ | Yalnızca sunucunuzda. Oturum açmış her kullanıcıyı imzalar; böylece kimse başka biri adına yazamaz. |
| Sunucu anahtarı (kendi sohbet ekranınız için) | lcsk_ | Yalnızca sunucunuzda. Yalnızca hazır sohbet yerine kendi sohbet ekranını oluşturan bir site için. |
| Yanıt imzalama anahtarı | lcwh_ | Yalnızca sunucunuzda. Sunucunuza gönderilen bir yanıtın SyBox'tan geldiğini kanıtlar. |
Hazır sohbet yalnızca kodu gerektirir. İmzalama anahtarı hesapları olan bir site içindir; sunucu anahtarı ve yanıt imzalama anahtarı ise yalnızca kendi sohbet ekranı olan bir site için.
Bir web sitesini kapatmak sohbeti hemen gizler. Sunucunuzun çağrıları yine kabul edilir ve kaydedilir.
Sohbeti sitenize ekleyin#
Kodu her sayfaya bir kez, kapanış body etiketinin hemen öncesine yapıştırın. Sohbet yalnızca Web sitesi adresleri listesindeki adreslerde görünür; rengi, konumu, başlığı ve karşılama mesajı kodda değil, SyBox'ta ayarlanır.
<script async src="https://api.sybox.io/lc/w.js" data-key="lcpk_YOUR_PUBLIC_KEY"></script>
WordPress: eklentimizi aynı pencereden indirin. Shopify: SyBox'un Shopify uygulamasında Bu mağazada Canlı Sohbet seçeneğini açın, ardından App embeds altında SyBox Live Chat'i açın — oturum açmış bir müşteri, başka hiçbir şey yapmanıza gerek kalmadan tanınır.
Oturum açmış kullanıcılar: imzalama anahtarı#
Bir kullanıcı sitenizde oturum açtığında, sunucunuz imzalama anahtarıyla imzalanmış kısa bir belirteçle — bir JWT, HS256 — sohbete kullanıcının kim olduğunu bildirir. Kullanıcının ardından her cihazda kendi adına tek bir sohbeti olur ve kimse başka birinin id'sini yazarak bu sohbeti açamaz.
- Oturum açıldıktan sonra sunucunuz belirteci aşağıdaki claim'lerden oluşturur.
- Sayfa, sohbetin kodundan önce belirteci sohbete
identity olarak iletir. - Sayfa yeniden yüklenmeden yapılan bir oturum açmadan sonra bunun yerine
SyBoxChat('identify', token) çağırın.
| Alan | Açıklama |
|---|
subzorunlustring | Kullanıcınızın id'si, en fazla 120 karakter. v_ ile başlayamaz. |
expzorunlunumber | Son geçerlilik zamanı, Unix saniyesi olarak — en fazla bir gün sonrası. On dakika uygundur: belirteç yalnızca sohbet açıldığında okunur. |
namestring | Sohbetin gösterdiği ad. |
emailstring | Kullanıcının e-posta adresi. |
phonestring | Kullanıcının telefon numarası. |
picturestring | Kullanıcının fotoğrafının https adresi, en fazla 500 karakter. SyBox kendi kopyasını saklar ve adres değiştiğinde bir sonraki oturum açmada fotoğrafı yeniden alır. |
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>
Belirteci yalnızca sunucunuzda oluşturun. Bir web sayfasındaki imzalama anahtarı, herkesin herhangi bir kullanıcınız adına yazmasına olanak tanır. Ayrıca belirtecin yerine sayfaya asla yalın bir kullanıcı id'si koymayın: bu, herkesin kimin sohbetini okuyacağını seçmesine olanak tanır.
Yalnızca sitemde oturum açmış kullanıcılar seçeneği açıkken, belirteci olmayan bir ziyaretçi sohbeti görmez.
Oturum açmadan önce sohbet etmiş bir ziyaretçiye, aynı tarayıcıda bir kez, bu sohbete hesabıyla devam etmek isteyip istemediği sorulur. Bunun için hiçbir şey yapmanız gerekmez.
Sohbeti sayfanızdan yönetin#
Sohbetin kodu sayfanıza tek bir fonksiyon ekler, 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'
};
Siteniz kapandıktan sonra bildirimler#
Sitenizin bir sayfası açıkken yanıt kendiliğinden bildirim olarak görünür. Siteniz kapandıktan sonra bir ziyaretçiye ulaşmak için tarayıcılar kendi alan adınızda bir service worker gerektirir: bu tek satırı sitenizin kökünde /sybox-lc-sw.js olarak, application/javascript türüyle sunun.
try { importScripts('https://api.sybox.io/lc/sw.js'); } catch (e) {}
Modu SyBox'ta (Web sitesi kapalıyken bildirimler) veya sayfada serviceWorker ile seçin: "auto" (yukarıdaki dosya, varsayılan), kendinize ait bir yol, "site" (aynı satırı kendi service worker'ınızın en üstüne koyun) veya "off".
Sohbet, sitenizde zaten bulunan bir service worker'ı asla değiştirmez ve dosyada fetch işleyicisi yoktur; bu nedenle sitenizin hiçbir isteğini görmez. WordPress eklentisi dosyayı sizin yerinize sunar. iPhone Safari, sıradan bir web sitesi için bildirim göstermez.
Kendi sohbet ekranınız: sunucu anahtarı#
Kendi sohbet ekranı olan bir site veya uygulama, hazır sohbeti kullanmak yerine sunucusundan SyBox ile konuşur. Her çağrı Authorization: Bearer lcsk_… taşır. Sunucunuz kullanıcıyı kendi id'nizle tanımlar ve anahtarı elinde tuttuğu için buna güvenilir.
| Çağrı | Açıklama |
|---|
POST /v1/livechat/messages | Kullanıcınız yazar. client_id yeniden denemeyi güvenli kılar: aynı id yalnızca bir kez kaydedilir ("duplicate": 1). |
POST /v1/livechat/upload | Multipart form verisi olarak bir dosya: file, user_id, client_id. En fazla 16 MB: görseller, ses, video, PDF, metin ve Office belgeleri. |
GET /v1/livechat/messages?user_id=&since=&before=&limit= | Sohbet: since sonrasındaki mesajlar ve agents_read_id, yani kullanıcınızın bir temsilcinin okuduğu en yeni mesajı. |
POST /v1/livechat/read | Kullanıcınız yanıtları up_to değerine kadar okudu. |
POST /v1/livechat/edit | Kullanıcınız kendi mesajını 15 dakika içinde düzenler. |
POST /v1/livechat/delete | Kullanıcınız kendi mesajını 24 saat içinde herkes için siler. |
POST /v1/livechat/react | Herhangi bir mesaja tepki; boş bir emoji tepkiyi kaldırır. |
POST /v1/livechat/typing | Kullanıcınız yazıyor: birkaç saniyede bir on: 1, yazmayı bıraktığında on: 0 gönderin. |
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
Geçmişte ve yanıtlarda bir mesaj:
{
"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
}
Hiç yazmamış bir kullanıcının geçmişi 404 unknown_user yanıtını verir: boş bir sohbet gösterin.
Medya bağlantıları imzalıdır ve bir saat geçerlidir; yenisi için tekrar isteyin. SyBox, gönderdiğiniz bir adresten asla dosya almaz: dosyayı yükleyin. Web sitesi başına dakikada en fazla 120 çağrı.
Sunucunuzdaki yanıtlar: yanıt imzalama anahtarı#
Web sitesinde Yanıtlar için sunucu adresiniz alanını doldurun. Ekibiniz yanıt verdiğinde, düzenlediğinde, sildiğinde veya okuduğunda SyBox olayı, yanıt imzalama anahtarıyla imzalanmış olarak oraya POST eder.
| Başlık | Açıklama |
|---|
x-sybox-signature | t=TIMESTAMP,v1=SIGNATURE — TIMESTAMP Unix saniyesi cinsinden, SIGNATURE ise TIMESTAMP.BODY (ham gövde) için hex biçiminde HMAC-SHA256; yanıt imzalama anahtarıyla hesaplanır. |
x-sybox-event | message.created, message.updated, message.deleted veya messages.read. |
x-sybox-delivery | Bir olayın her yeniden denemesinde aynıdır: tekrarı yok saymak için kullanın. |
Gövde event, line_id, user_id ve message içerir — silme için ise message_id; read_id de messages.read içindir: kullanıcınızın mesajları bu id'ye kadar okunmuştur.
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();
});
İmzayı ham gövde üzerinde kontrol edin, beş dakikadan eski bir t değerini reddedin ve hızla 2xx ile yanıt verin. Bunun dışındaki her şey 10 sn, 30 sn, 2 dk, 10 dk ve 30 dk sonra yeniden denenir, ardından bırakılır. SyBox yalnızca herkese açık adresleri çağırır ve asla yönlendirmeyi izlemez.
Neden kodları#
Her Canlı Sohbet yanıtı JSON'dur — {"ok": 1, …} veya cümle yerine bir kod içeren {"ok": 0, "error": "CODE"}:
| Kod | Durum | Açıklama |
|---|
unauthorized | 401 | Sunucu anahtarı yanlış veya değiştirilmiş. |
invalid_json invalid_payload invalid_user_id | 400 | İstek beklenen biçimde değil. |
empty_message no_file empty_file | 400 | Metin de dosya da yok. |
too_long | 400 | Düzenlenen metin 4.096 karakteri aşıyor. |
not_an_emoji | 400 | emoji bir emoji değil. |
not_found | 404 | Böyle bir mesaj yok veya bu kullanıcıya ait değil. |
too_late | 409 | Düzenleme veya silme süresi doldu. |
file_type_not_allowed | 400 | Bu dosya türü kabul edilmiyor. |
file_too_large | 413 | Dosya 16 MB'ı aşıyor. |
blocked | 403 | Ekibiniz bu kullanıcıyı engelledi. |
unknown_user | 404 | Bu kullanıcı id'si için henüz sohbet yok. |
rate_limited | 429 | Çok fazla çağrı: yavaşlayın ve tekrar deneyin. |
maintenance | 503 | SyBox güncelleniyor: Retry-After saniye sonra tekrar deneyin. |
server_error | 500 | Bizim tarafımızda bir sorun oluştu. |
Bir anahtarı değiştirme#
Bir anahtarı mı kaybettiniz veya sızdığını mı düşünüyorsunuz? Web sitesinin kartında Yeni imzalama anahtarı, Yeni sunucu anahtarı veya Yeni yanıt imzalama anahtarı'na tıklayın. Yeni bir sunucu anahtarı, eskisini bir gün daha çalışır durumda bırakır; böylece kesinti olmadan geçiş yapabilirsiniz. Diğer anahtarlar ise hemen durur.