WhatsApp API
Kirim pesan pertama Anda#
Pesan teks biasa. Bagian body menggunakan payload standar Cloud API — kami meneruskannya langsung ke Meta tanpa perubahan apa pun.
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());
Respons#
Anda menerima respons persis seperti yang dikembalikan Meta, lengkap dengan kode status HTTP yang sama — sehingga library client Cloud API yang ada dapat memprosesnya tanpa perubahan.
200 OK · application/json {
"messaging_product": "whatsapp",
"contacts": [{ "input": "15551234567", "wa_id": "15551234567" }],
"messages": [{ "id": "wamid.HBgLMTU1NTEyMzQ1NjcVAgARGBI…" }]
}
Sistem Anda di SyBox
Tampilkan data Anda di samping chat#
Saat agen membuka percakapan, SyBox memanggil endpoint ANDA dan menampilkan data yang Anda kirim tepat di samping chat — pesanan pelanggan, saldo, tiket. Kami mengirim nomor ponsel pelanggan, atau email pelanggan untuk percakapan email.
Tambahkan endpoint Anda di Pengaturan › Integrasi › API ERP. Setiap panggilan adalah POST dengan kunci Anda di Authorization dan permintaan dalam satu kolom, data, berisi string JSON — sehingga kedua pihak dapat menambahkan kolom nanti tanpa merusak pihak lain. Jawab dalam 15 detik.
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" }
]
}
Setiap panggilan juga menyebutkan siapa yang bertanya: X-Sybox-Lang (bahasa pengguna — jawab dalam bahasa tersebut), X-Sybox-Theme (light atau dark, untuk balasan html), X-Sybox-Agent (id pengguna), dan X-Sybox-Number (nomor tempat percakapan berlangsung).
Kami mengirim nomor ponsel dan email — salah satunya bisa saja kosong. Cocokkan dengan data pengenal pelanggan di sistem Anda, lalu balas dengan { renderAs, content }.
renderAs#
Atur renderAs untuk memberi tahu SyBox cara menampilkan konten Anda — salah satu dari:
| renderAs | Deskripsi |
|---|
table | Baris dan kolom. content adalah array berisi objek datar (atau { rows: [ … ] }); kunci-kuncinya menjadi judul kolom. |
cards · kpi | Ubin kecil, seperti saldo atau jumlah pesanan. content adalah array berisi { label, value } (atau { cards: [ … ] }). |
feed · list · thread | Linimasa berisi kartu. content adalah array berisi { title, text, date, tags, media: [{ type, url, name }] }. |
message | Satu catatan yang ditata sebagai satu kartu bergaya pesan. |
html | HTML Anda sendiri, sebagai string di content. Hanya untuk markup yang Anda buat sendiri. |
sections | Beberapa blok dalam satu balasan: content adalah { sections: [ { title, renderAs, content } ] }. |
json | Bawaan jika renderAs tidak diisi: SyBox menampilkan content Anda sebagai JSON yang diformat. |
Tambahkan sybox_ref ke baris tabel atau kartu mana pun agar bisa diklik — SyBox akan memanggil endpoint Anda kembali dengan ref tersebut agar Anda dapat menampilkan detail data tersebut.
Pencarian produk (X-Sybox-Action: products)#
Endpoint yang sama juga dapat merespons pencarian produk. Pengguna mengetik apa yang dicari pelanggan, SyBox mengirimkannya sebagai "q", dan balasan Anda ditampilkan sebagai kartu produk di samping obrolan — dengan sekali ketuk Kirim yang memasukkan tautan produk ke editor pesan.
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"
}
]
}
| Kolom | Deskripsi |
|---|
namewajib | Satu-satunya kolom wajib. Produk yang hanya memiliki nama tetap akan tampil rapi sebagai kartu. |
price · oldPrice · currency | Gunakan angka, bukan string terformat. Kirim oldPrice hanya jika memang ada harga sebelumnya — persentase diskon akan dihitung otomatis dari kedua harga tersebut. |
available · stock | available bernilai true/false; stock adalah jumlah opsional yang ditampilkan di sampingnya. Abaikan kedua kolom ini jika Anda tidak melacak stok — kolom yang tidak ada tidak akan menampilkan apa pun dan tidak pernah dianggap habis stok. |
image | URL gambar langsung dengan protokol https. Gambar apa pun yang gagal dimuat akan digantikan dengan placeholder netral. |
link | URL halaman produk. Tanpa URL ini kartu hanya dapat dilihat (read-only) — tombol Kirim ke chat hanya muncul jika ada link untuk dikirim. |
note | Satu baris teks singkat di bawah harga — waktu pengiriman, varian, atau kondisi barang. |
Cukup gunakan endpoint yang sudah Anda buat: URL yang sama, key yang sama, struktur { "data": "…" } yang sama. Periksa X-Sybox-Action untuk membedakan antara pencarian produk dan pencarian pelanggan. Berikan respons berupa content sebagai array biasa (atau { items: [ … ] }).
Peristiwa
Webhook: peristiwa di server Anda#
Tambahkan endpoint Anda di Pengaturan › Integrasi › Webhook, pilih peristiwa dan nomornya, lalu SyBox mengirim POST setiap peristiwa ke sana saat terjadi. Peristiwa WhatsApp tiba dalam format webhook Meta sendiri, sehingga handler yang ditulis untuk Meta bekerja tanpa perubahan.
| Peristiwa | Deskripsi |
|---|
messagePesan masuk | Pesan dari pelanggan. |
statusStatus pesan | Terkirim, diterima, dibaca, atau gagal — untuk pesan yang Anda kirim. |
reactionReaksi | Pelanggan memberi reaksi pada sebuah pesan. |
donePercakapan ditandai selesai | Percakapan ditandai selesai: oleh seseorang, oleh otomatisasi, atau dengan sendirinya setelah pelanggan tidak membalas. |
labelLabel ditambahkan atau dihapus | Label ditambahkan ke percakapan, atau dihapus darinya. |
contactKontak dibuat atau diperbarui | Kontak dibuat, atau nama, email, atau kolom kontaknya berubah. |
dealKesepakatan ditutup | Kesepakatan yang ditutup dicatat dalam percakapan. |
caseKasus CRM dibuka atau ditutup | Kasus CRM dibuka, atau ditutup (terselesaikan, tidak terselesaikan, atau dibatalkan). |
Permintaan · SyBox → Anda 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?" }
}]
}
}]
}]
}
Media. Tautan milik Meta memerlukan token Meta untuk dibuka, jadi SyBox menggantinya dengan tautan bertanda tangan ke salinan file milik SyBox sendiri, yang berlaku selama 24 jam. Pesan suara juga dapat membawa kata-katanya di sybox.transcription, jika webhook memintanya.
Saluran lain — Messenger, Instagram, Telegram, dan Live Chat — tiba dalam format SyBox sendiri, hanya untuk peristiwa pesan:
{
"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" }
}
}
Peristiwa percakapan tiba dalam format SyBox sendiri, untuk setiap saluran. event menyebutkan secara tepat apa yang terjadi:
| Peristiwa | <code>event</code> |
|---|
| Percakapan ditandai selesai | conversation.done |
| Label ditambahkan atau dihapus | label.added · label.removed |
| Kontak dibuat atau diperbarui | contact.created · contact.updated |
| Kesepakatan ditutup | deal.closed |
| Kasus CRM dibuka atau ditutup | case.opened · case.closed |
| Peristiwa uji | 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" }
}
Signing secret#
| Header | Deskripsi |
|---|
X-Hub-Signature-256 | sha256= diikuti HMAC-SHA256 heksadesimal dari body mentah, dengan kunci rahasia penandatanganan webhook — header dan metode yang sama dengan milik Meta. |
x-sybox-signature | Nilai yang sama, agar endpoint yang juga menerima panggilan dari Meta sendiri dapat membedakan panggilan kami. |
x-sybox-event | Apa yang terjadi: message, status, reaction, salah satu peristiwa percakapan di bawah ini (conversation.done, label.added…), atau test. |
x-sybox-timestamp | Waktu pengirimannya, dalam detik Unix. |
x-sybox-signature-rsa | Tanda tangan kedua: rsa-sha256= diikuti RSA-SHA256 (PKCS#1 v1.5) dalam base64 dari x-sybox-timestamp, sebuah titik, dan body mentah. |
x-sybox-key-id | Kunci kami yang mana yang membuatnya. Kunci publiknya ada di https://api.sybox.io/.well-known/sybox-webhook-key. |
Periksa tanda tangan pada byte mentah, sebelum Anda mem-parse-nya, dan bandingkan dalam waktu konstan:
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();
});
Tidak ingin menyimpan kunci rahasia bersama di server Anda? Periksa x-sybox-signature-rsa dengan kunci publik kami sebagai gantinya. Dengan cara mana pun, tolak timestamp yang lebih dari lima menit.
Jawab dengan 2xx apa pun dalam 15 detik. Selain itu — error, waktu habis, atau pengalihan — akan dicoba lagi setelah 1, 2, 4, 8, 16, dan 32 menit, lalu dibuang. Satu peristiwa bisa tiba lebih dari sekali: gunakan id pesan untuk mengabaikan pengulangan. Alamatnya harus publik dan https.
Webhook yang gagal 20 kali berturut-turut selama lebih dari satu jam akan dinonaktifkan secara otomatis dan admin ruang kerja akan diberi tahu. Peristiwa yang menunggu disimpan selama 24 jam: aktifkan kembali untuk mengirimkannya.
Untuk menguji, tekan Kirim peristiwa uji pada kartu webhook. Pengiriman, pada kartu yang sama, menampilkan 50 pengiriman terakhir beserta jawaban server Anda, dan dapat mengirimkannya lagi.
Live Chat
Live Chat#
Pasang chat di situs web Anda: pengunjung menulis di sana, tim Anda menjawab di SyBox. Tambahkan situs web di Pengaturan › Pesan › Saluran, pada kartu Live Chat; jendela yang terbuka, Kode untuk situs Anda, memberi Anda kode dan kunci di bawah ini. Ketiga kunci rahasia hanya ditampilkan satu kali.
| Kunci | Diawali dengan | Kegunaannya |
|---|
| Kunci publik | lcpk_ | Dipasang di halaman Anda. Aman untuk dipublikasikan: kunci ini hanya menyebutkan situs web, dan chat hanya muncul di Alamat situs web Anda. |
| Kunci penandatanganan untuk pengguna yang telah masuk | lcid_ | Hanya di server Anda. Menandatangani setiap pengguna yang telah masuk, sehingga tidak ada yang bisa menulis atas nama orang lain. |
| Kunci server (untuk layar chat Anda sendiri) | lcsk_ | Hanya di server Anda. Hanya untuk situs yang membuat layar chat sendiri, bukan memakai chat siap pakai. |
| Kunci tanda tangan balasan | lcwh_ | Hanya di server Anda. Membuktikan bahwa balasan yang dikirim ke server Anda berasal dari SyBox. |
Chat siap pakai hanya memerlukan kode. Kunci penandatanganan untuk situs yang memiliki akun pengguna; kunci server dan kunci tanda tangan balasan hanya untuk situs dengan layar chat sendiri.
Menonaktifkan situs web langsung menyembunyikan chat. Panggilan dari server Anda tetap diterima dan disimpan.
Tambahkan chat ke situs Anda#
Tempel kode sekali di setiap halaman, tepat sebelum tag penutup body. Chat hanya muncul di alamat yang ada di Alamat situs web, dan warna, posisi, judul, serta sambutannya diatur di SyBox, bukan di kode.
<script async src="https://api.sybox.io/lc/w.js" data-key="lcpk_YOUR_PUBLIC_KEY"></script>
WordPress: unduh plugin kami dari jendela yang sama. Shopify: di aplikasi Shopify SyBox, aktifkan Live Chat di toko ini, lalu SyBox Live Chat di bawah Penyematan aplikasi — pelanggan yang telah masuk langsung dikenali tanpa langkah tambahan.
Pengguna yang telah masuk: kunci penandatanganan#
Saat pengguna telah masuk ke situs Anda, server Anda memberi tahu chat siapa mereka dengan token singkat — JWT, HS256 — yang ditandatangani dengan kunci penandatanganan. Pengguna tersebut lalu memiliki satu percakapan atas namanya, di setiap perangkat, dan tidak ada yang bisa membukanya dengan mengetik id orang lain.
- Setelah pengguna masuk, server Anda membuat token dari klaim di bawah ini.
- Halaman meneruskannya ke chat sebagai
identity, sebelum kode chat. - Setelah pengguna masuk tanpa memuat ulang halaman, panggil
SyBoxChat('identify', token) sebagai gantinya.
| Kolom | Deskripsi |
|---|
subwajibstring | Id pengguna Anda, hingga 120 karakter. Tidak boleh diawali dengan v_. |
expwajibnumber | Waktu kedaluwarsa, dalam detik Unix — tidak lebih dari satu hari ke depan. Sepuluh menit sudah tepat: token hanya dibaca saat chat dibuka. |
namestring | Nama yang ditampilkan percakapan. |
emailstring | Alamat email pengguna. |
phonestring | Nomor telepon pengguna. |
picturestring | Alamat https foto pengguna, hingga 500 karakter. SyBox menyimpan salinannya sendiri, dan mengambilnya lagi saat pengguna masuk berikutnya jika alamatnya berubah. |
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>
Buat token hanya di server Anda. Kunci penandatanganan di halaman web memungkinkan siapa pun menulis atas nama pengguna Anda mana pun. Dan jangan pernah menaruh id pengguna begitu saja di halaman sebagai pengganti token: itu memungkinkan siapa pun memilih percakapan siapa yang ingin dibaca.
Jika Hanya pengguna yang telah masuk di situs saya diaktifkan, pengunjung tanpa token tidak akan melihat chat.
Pengunjung yang sudah chat sebelum masuk akan ditanya sekali, di browser yang sama, apakah ingin melanjutkan percakapan itu dengan akunnya. Anda tidak perlu melakukan apa pun untuk ini.
Kendalikan chat dari halaman Anda#
Kode chat menambahkan satu fungsi ke halaman Anda, 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'
};
Notifikasi setelah situs Anda ditutup#
Selama halaman situs Anda terbuka, balasan muncul sebagai notifikasi dengan sendirinya. Untuk menjangkau pengunjung setelah situs Anda ditutup, browser memerlukan service worker di domain Anda sendiri: sajikan satu baris ini sebagai /sybox-lc-sw.js di root situs Anda, sebagai application/javascript.
try { importScripts('https://api.sybox.io/lc/sw.js'); } catch (e) {}
Pilih mode di SyBox (Notifikasi saat situs web ditutup) atau di halaman dengan serviceWorker: "auto" (file di atas, bawaan), path milik Anda sendiri, "site" (letakkan baris yang sama di bagian atas service worker Anda sendiri), atau "off".
Chat tidak pernah menggantikan service worker yang sudah dimiliki situs Anda, dan file ini tidak memiliki fetch handler, sehingga tidak pernah melihat permintaan situs Anda. Plugin WordPress menyajikan file ini untuk Anda. Safari di iPhone tidak menampilkan notifikasi untuk situs web biasa.
Layar chat Anda sendiri: kunci server#
Situs atau aplikasi dengan layar chat sendiri berkomunikasi dengan SyBox dari servernya, bukan memakai chat siap pakai. Setiap panggilan membawa Authorization: Bearer lcsk_…. Server Anda menyebut pengguna dengan id Anda sendiri, dan dipercaya untuk itu, karena server Anda memegang kuncinya.
| Panggilan | Deskripsi |
|---|
POST /v1/livechat/messages | Pengguna Anda menulis. client_id membuat percobaan ulang aman: id yang sama hanya disimpan sekali ("duplicate": 1). |
POST /v1/livechat/upload | Sebuah file, sebagai multipart form data: file, user_id, client_id. Hingga 16 MB: gambar, suara, video, PDF, teks, dan dokumen Office. |
GET /v1/livechat/messages?user_id=&since=&before=&limit= | Percakapan: pesan setelah since, dan agents_read_id, pesan terbaru dari pengguna Anda yang sudah dibaca agen. |
POST /v1/livechat/read | Pengguna Anda telah membaca balasan hingga up_to. |
POST /v1/livechat/edit | Pengguna Anda mengedit pesannya sendiri, dalam 15 menit. |
POST /v1/livechat/delete | Pengguna Anda menghapus pesannya sendiri untuk semua orang, dalam 24 jam. |
POST /v1/livechat/react | Reaksi pada pesan apa pun; emoji kosong akan menghapusnya. |
POST /v1/livechat/typing | Pengguna Anda sedang mengetik: kirim on: 1 setiap beberapa detik dan on: 0 saat berhenti. |
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
Sebuah pesan, di riwayat dan di balasan:
{
"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
}
Riwayat pengguna yang belum pernah menulis menjawab 404 unknown_user: tampilkan percakapan kosong.
Tautan media bertanda tangan dan berlaku selama satu jam; minta lagi untuk mendapatkan yang baru. SyBox tidak pernah mengambil file dari alamat yang Anda kirim: unggah file tersebut. Hingga 120 panggilan per menit untuk setiap situs web.
Balasan di server Anda: kunci tanda tangan balasan#
Isi Alamat server Anda untuk balasan pada situs web. Saat tim Anda membalas, mengedit, menghapus, atau membaca, SyBox mengirim POST peristiwa tersebut ke sana, ditandatangani dengan kunci tanda tangan balasan.
| Header | Deskripsi |
|---|
x-sybox-signature | t=TIMESTAMP,v1=SIGNATURE — TIMESTAMP dalam detik Unix, SIGNATURE adalah HMAC-SHA256 dalam hex dari TIMESTAMP.BODY (body mentah), dengan kunci tanda tangan balasan sebagai kuncinya. |
x-sybox-event | message.created, message.updated, message.deleted, atau messages.read. |
x-sybox-delivery | Sama di setiap percobaan ulang untuk satu peristiwa: gunakan untuk mengabaikan pengulangan. |
Body berisi event, line_id, user_id, dan message — atau message_id untuk penghapusan, dan read_id untuk messages.read: pesan pengguna Anda sudah dibaca hingga id tersebut.
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();
});
Periksa tanda tangan pada body mentah, tolak t yang lebih dari lima menit, dan segera jawab dengan 2xx. Selain itu akan dicoba lagi setelah 10 dtk, 30 dtk, 2 mnt, 10 mnt, dan 30 mnt, lalu dibuang. SyBox hanya memanggil alamat publik dan tidak pernah mengikuti pengalihan.
Kode alasan#
Setiap jawaban Live Chat berupa JSON — {"ok": 1, …}, atau {"ok": 0, "error": "CODE"} dengan kode, bukan kalimat:
| Kode | Status | Deskripsi |
|---|
unauthorized | 401 | Kunci server salah atau sudah diganti. |
invalid_json invalid_payload invalid_user_id | 400 | Permintaan tidak dalam bentuk yang diharapkan. |
empty_message no_file empty_file | 400 | Tidak ada teks dan tidak ada file. |
too_long | 400 | Teks yang diedit lebih dari 4.096 karakter. |
not_an_emoji | 400 | emoji bukan emoji. |
not_found | 404 | Pesan tidak ada, atau bukan milik pengguna ini. |
too_late | 409 | Waktu untuk mengedit atau menghapus sudah lewat. |
file_type_not_allowed | 400 | Jenis file ini tidak diterima. |
file_too_large | 413 | File lebih dari 16 MB. |
blocked | 403 | Tim Anda telah memblokir pengguna ini. |
unknown_user | 404 | Belum ada percakapan untuk id pengguna ini. |
rate_limited | 429 | Terlalu banyak panggilan: perlambat dan coba lagi. |
maintenance | 503 | SyBox sedang diperbarui: coba lagi setelah Retry-After detik. |
server_error | 500 | Terjadi kegagalan di pihak kami. |
Mengganti kunci#
Kehilangan kunci, atau mengira kunci bocor? Di kartu situs web, klik Kunci penandatanganan baru, Kunci server baru, atau Kunci tanda tangan balasan baru. Kunci server baru membuat kunci lama tetap berfungsi selama satu hari, sehingga Anda dapat beralih tanpa jeda; kunci lainnya langsung berhenti.