Widget situs

Widget adalah chat bot Anda langsung di situs: tombol bundar di sudut halaman yang membuka percakapan. Bagi pengunjung, ini satu cara lagi untuk menghubungi Anda; bagi bot, ini kanal biasa — dengan reaksi yang sama dan dialog operator yang sama seperti di messenger. Ikhtisar multikanal ada di Saluran.

Pemasangannya adalah satu potongan kode yang ditempel ke templat situs. Selebihnya diatur di panel dan diterapkan tanpa menyentuh situs lagi.

Di mana mengambil kode

Di panel buka Widget → blok Pemasangan di situs → kolom Kode untuk disisipkan. Tombol Salin menaruh seluruh potongan ke papan klip.

Kodenya seperti ini, dengan kunci Anda sendiri menggantikan titik-titik:

<script>
  window.mybot = { key: "eu-1a2b3c4d-..." };
</script>
<script async src="https://getmybot.dev/loader.js"></script>

Jika alih-alih kode Anda melihat keterangan bahwa kunci instalasi untuk bot ini belum diterbitkan, berarti kanal widget belum tersambung. Hubungi dukungan: kunci diterbitkan saat kanal disambungkan.

Di bawah kode ada petunjuk host mana yang akan dihubungi widget. Jika situs Anda memakai CSP (Content Security Policy), izinkan host tersebut memuat skrip dan menerima permintaan; kalau tidak, peramban akan memblokir widget tanpa suara.

Di mana kode diletakkan

Potongan itu masuk ke HTML setiap halaman tempat widget harus muncul. Dalam praktiknya: sekali saja di templat bersama — footer, kolom «kode sebelum </body>» pada CMS, atau kontainer di tag manager.

Aturannya singkat:

  • Tempat terbaik adalah tepat sebelum </body> penutup. Di <head> juga bisa, tetapi peramban akan menghabiskan waktu untuk widget sebelum konten Anda.
  • Urutan kedua tag itu penting: yang pertama menetapkan kunci, yang kedua memuat widget. Jangan ditukar dan jangan dipisah di halaman.
  • Tag kedua bertanda async dan tidak memblokir render. Jangan hapus atribut itu.
  • Satu potongan per halaman. Dua salinan berarti dua percobaan menjalankan widget.

Selanjutnya pemuat sendiri menentukan pusat data mana yang melayani bot Anda dan mengambil kode widget dari sana. Tidak ada yang perlu Anda lakukan untuk itu.

Kunci instalasi bersifat publik

Kunci pada tag pertama adalah pengenal publik bot Anda, bukan kata sandi. Ia ada di kode sumber halaman: pengunjung mana pun bisa membuka «Lihat sumber halaman» dan membacanya. Semua widget chat bekerja begitu, dan itu wajar.

Yang penting:

  • Dengan kunci itu orang tidak bisa masuk ke panel, membaca dialog orang lain, mengekspor basis pelanggan, atau mengubah apa pun pada bot. Ia membuka tepat satu hal: memulai percakapan baru dengan bot ini.
  • Jangan perlakukan kunci sebagai rahasia: menyembunyikan atau mengaburkannya tidak ada gunanya.
  • Jika kunci perlu diganti (misalnya setelah berpisah dengan vendor), dukungan yang melakukannya. Setelah penggantian, kode lama di situs berhenti bekerja dan harus diperbarui.

Daftar domain yang diizinkan

Widget punya daftar domain yang diizinkan (origin allowlist): alamat situs tempat widget boleh bekerja. Daftar ini ditetapkan saat kanal disambungkan dan bisa diubah kemudian tanpa menerbitkan ulang kunci, sehingga kode di situs Anda tetap sama.

Entrinya berupa origin: skema, host, dan port jika bukan port standar.

https://example.com
https://www.example.com
https://shop.example.com
http://localhost:3000

Pencocokannya persis, karakter demi karakter:

  • Tidak ada wildcard. https://*.example.com tidak akan bekerja — daftar subdomain satu per satu.
  • example.com dan www.example.com adalah entri berbeda. Jika situs menjawab di keduanya, tambahkan keduanya.
  • http:// dan https:// juga entri berbeda. Biasanya cukup https://, tetapi lingkungan uji di http:// harus ditambahkan secara eksplisit.
  • Tanpa jalur: https://example.com/shop ditolak; origin hanyalah alamat situs.

Jika domain asli Anda tidak ada dalam daftar, widget tidak akan berjalan. Peramban pengunjung menerima penolakan «origin not allowed» dan chat sama sekali tidak terbuka. Ini biasanya muncul setelah pindah ke domain baru, menambah subdomain, atau meluncurkan versi bahasa kedua di alamat tersendiri — masing-masing perlu entrinya sendiri.

Jika daftar kosong, tidak ada pembatasan dan widget bekerja dari mana saja. Anggap itu «belum dikonfigurasi», bukan keterbukaan yang disengaja: begitu Anda tahu domain Anda, tuliskan.

Apa yang dilindungi pembatasan ini dan apa yang tidak

Melindungi: situs lain tidak bisa menanamkan widget Anda. Kalau tidak, seseorang bisa menyalin potongan kode ke halamannya dan peramban pengunjung sungguhan akan menyerahkan isi percakapan dengan bot Anda ke situs itu. Persis itulah yang ditutup daftar tersebut.

Tidak melindungi: ini bukan perlindungan dari orang yang menyalin kunci lalu memanggil platform secara langsung — bukan dari peramban, melainkan misalnya dengan skrip. Header berisi alamat situs dipasang oleh peramban; program di luar peramban bisa tidak mengirimkannya atau mengirim apa saja. Jadi anggaplah daftar ini pembatas penanaman, bukan batas keamanan. Yang melindungi dari penyalahgunaan adalah pembatasan laju dan moderasi dialog di sisi platform, bukan daftar ini.

Tampilan

Di WidgetTampilan diatur:

  • Posisi — tombol di sudut kiri atau kanan bawah.
  • Warna aksen — warna tombol dan elemen chat. Pakai warna merek Anda agar widget tidak terlihat asing.
  • Nama chat — label tombol dan judul panel chat. Biasanya nama perusahaan atau nama yang dipakai bot memperkenalkan diri.

Di sebelahnya ada pratinjau — widget hidup yang menunjukkan hasilnya sebelum disimpan.

Widget belum punya pesan sambutan: hanya tiga pengaturan itu. Pesan pertama dikirim bot sebagai jawaban atas pengunjung, mengikuti reaksi Anda yang biasa.

Perubahan sampai ke situs dengan sendirinya: widget membaca pengaturannya saat halaman dimuat, jadi kode yang ditempel tidak pernah perlu disunting.

Pengunjung bersifat anonim

Pengunjung tidak mengisi apa pun untuk menulis. Pada peluncuran pertama widget menerima pengenal pengunjung anonim dari platform dan menyimpannya di peramban, supaya saat kembali orang itu melihat percakapan sebelumnya, bukan chat kosong.

Konsekuensinya:

  • Di dialog, pengunjung semacam itu tampak anonim: tanpa nama, telepon, dan email sampai ia sendiri menuliskannya.
  • Pengenal itu hidup di satu peramban tertentu. Peramban lain, perangkat lain, atau data situs yang dibersihkan berarti pengunjung baru dengan riwayat kosong.
  • Pengunjung anonim bisa dikaitkan dengan pelanggan yang sudah Anda kenal, misalnya pengguna yang login di area anggota Anda. Caranya ada di bagian berikut.

Mengaitkan pengunjung dengan pengguna Anda

Jika pengunjung sudah login di situs Anda, Anda bisa memberi tahu platform siapa dia. Setelah dikaitkan, percakapan berhenti menjadi anonim: ia digabung ke profil orang tersebut, dan pesan-pesan anonimnya yang lalu tidak hilang.

Cukup satu panggilan, dengan fungsi yang menghasilkan bukti:

mybot.identify(async (visitorId) => {
  const res = await fetch("/mybot-sign", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ userId: currentUser.id, visitorId }),
  });
  return res.json(); // { userId, signature, expiresAt }
});

Widget memanggil fungsi Anda dengan visitorId — identitas pengunjung anonim saat ini untuk peramban ini, satu-satunya nilai yang hanya diketahui widget — dan menunggu fungsi itu mengembalikan, atau resolve menjadi, { userId, signature, expiresAt }. Satu-satunya tugas fungsi Anda adalah menyerahkan visitorId ke server Anda sendiri, bersama identitas pengguna yang sudah login (currentUser.id di atas adalah apa pun bentuknya di situs Anda), dan mengembalikan persis apa yang dijawab server Anda. Penandatanganan sebenarnya terjadi di server Anda, bukan di fungsi ini — lihat di bawah.

Sebuah callback, bukan nilai yang sudah dihitung atau cara membaca visitorId secara langsung, karena dua alasan:

  • Waktu keberadaan. Widget membuat visitorId secara asinkron saat memulai — nilai ini belum ada pada saat skrip loader dijalankan, dan mybot.identify sendiri baru dipasang setelah proses itu selesai (detail di bawah). Jadi, pada saat halaman Anda bisa memanggil mybot.identify sama sekali, identitas yang diterima fungsi Anda dijamin nyata. Getter biasa tidak akan punya jaminan itu: tidak ada yang mencegah halaman membacanya satu baris terlalu awal dan tidak mendapat apa-apa, diam-diam menghasilkan tanda tangan yang tidak pernah cocok tanpa petunjuk apa pun kenapa.
  • Cakupan. Kode halaman Anda sendiri tidak pernah perlu menyimpan atau memindahkan visitorId secara manual — nilai itu hanya ada di dalam fungsi tunggal ini, untuk satu panggilan yang dibutuhkannya.

Sejak meninggalkan widget, visitorId adalah kemampuan pembawa (bearer capability): siapa pun yang bisa mendapatkan tanda tangan untuknya dapat digabungkan ke sesi pengunjung tersebut. Kirim hanya ke server Anda sendiri, lewat permintaan terautentikasi Anda sendiri, dan tidak ke mana pun lagi — jangan catat di log, jangan teruskan ke pihak ketiga, dan jangan taruh dalam panggilan analitik sisi klien.

Tanda tangan dihitung hanya di server Anda sendiri

Di dalam fungsi Anda di atas, server Anda sendiri — tidak pernah peramban — menghitung tanda tangan: HMAC-SHA256 dengan kunci rahasia widget, dalam heksadesimal, 32 karakter pertama, dari tiga nilai yang digabung menjadi satu pesan. Rumus yang sama ditampilkan di panel, di sebelah rahasia, di bawah «Signing algorithm»:

signature = hex(HMAC-SHA256(key: secret, message: userId + "\n" + visitorId + "\n" + expiresAt)).slice(0, 32)

Setiap + "\n" + di atas adalah karakter baris baru sungguhan di antara bagian-bagian itu, bukan dua karakter garis miring terbalik dan n. Jika fungsi HMAC Anda menerima pesan sebagai satu string, gabungkan tiga bagian itu dengan baris baru yang nyata — tiga panggilan .update() terpisah tanpa baris baru di antaranya akan meng-hash pesan yang berbeda dan salah.

Tiga bagiannya:

  • userId — identitas pengguna yang sudah login, nilai yang sama yang diterima server Anda dari fungsi di atas dan dikembalikan dalam responsnya.
  • visitorId — identitas pengunjung anonim yang diterima fungsi Anda sebagai argumen dan diteruskan tanpa perubahan ke server Anda.
  • expiresAtstempel waktu unix dalam detik (bukan milidetik), sampai kapan tanda tangan khusus ini tetap berlaku, dibuat oleh server Anda saat menandatangani. Platform menolak panggilan yang expiresAt-nya sudah lewat, dan juga yang lebih dari 24 jam di masa depan — tanda tangani tepat sebelum mengembalikannya dari fungsi Anda, bukan sekali lalu di-cache untuk permintaan berikutnya.

Batas waktu ini adalah inti dari perubahan ini, bukan detail sampingan. Rumus sebelumnya hanya mencakup identitas pengguna, sehingga tanda tangan yang tertangkap sekali saja — tercatat di suatu tempat, disadap dari lalu lintas jaringan, apa pun caranya — tetap berlaku selamanya dan cocok untuk siapa pun pengunjungnya, bukan hanya yang menjadi tujuan penerbitannya. Siapa pun yang mendapatkannya bisa memutarnya ulang di peramban yang sama sekali berbeda, dan platform akan menggabungkan riwayat penjelajahan anonim orang asing itu ke profil pelanggan sungguhan. Mengikat tanda tangan ke satu visitorId tertentu dan memberinya usia pendek menutup kedua ujung celah itu: tanda tangan hanya tervalidasi untuk sesi yang menjadi tujuannya, tidak untuk sesi lain, dan berhenti tervalidasi sama sekali begitu expiresAt terlewati — jadi bahkan tanda tangan yang bocor pun hanya menjadi risiko singkat untuk satu sesi, bukan risiko permanen.

Hitung ketiga nilainya di server Anda dan kembalikan dari fungsi Anda dalam bentuk jadi. Ini bukan formalitas: untuk menghitung tanda tangan di peramban, rahasianya harus dikirim ke sana, artinya diserahkan ke setiap pengunjung halaman. Sesudah itu siapa pun bisa mengaku sebagai pelanggan mana pun milik Anda dan membaca percakapannya. Mengembalikan tanda tangan (dan visitorId/expiresAt yang menjadi dasar perhitungannya) ke peramban aman; rahasianya tidak.

Panggilan itu sendiri tidak menampilkan apa pun di layar: pengaitan terjadi di sisi server dan diam-diam. Jika fungsi Anda melempar error atau promise-nya ditolak, atau platform menolak tanda tangan yang dihasilkan — tidak cocok, atau expiresAt tidak ada, sudah lewat, atau lebih dari 24 jam di masa depan — tidak ada yang terjadi lebih dari itu: pengunjung tidak menyadari apa pun dan terus menulis sebagai anonim.

mybot.identify tidak langsung ada — ia muncul setelah bundel utama widget termuat, bersama visitorId yang nyata (lihat "Waktu keberadaan" di atas). Panggil dari penangan pemuatan halaman, bukan sebagai baris pertama di <head>.

Di mana rahasianya dan bagaimana menggantinya

Rahasia widget adalah kunci yang Anda pakai menandatangani. Ia ada di pengaturan widget, pada bagian «identify() secret». Jika bagian itu menyebut widget belum tersambung, sambungkan dulu kanal web — rahasianya muncul bersamanya.

Menampilkan. Tombol «Reveal secret» mengambil nilainya. Nilai itu tidak muncul terbuka: mula-mula Anda melihat titik-titik, dan sakelar terpisah «Show» menampilkan karakternya («Mask» menyembunyikannya lagi). Di sebelahnya ada «Clear from screen» yang menghapus nilai dari tampilan.

Anda bisa menampilkannya berkali-kali, dan menampilkan tidak membatalkan apa pun: rahasianya tetap berlaku setelah layar ditutup. Begitu karena ia disimpan terenkripsi, bukan di-hash — platform butuh nilai aslinya untuk memverifikasi setiap tanda tangan. Kalau enam bulan lagi Anda merilis backend baru, tinggal buka dan baca lagi.

Setelah menyalin, bereskan sendiri. Papan klip tidak bisa dikosongkan oleh platform: dari peramban hal itu tidak bisa dilakukan dengan andal, jadi kami tidak menjanjikannya. Hapus nilai dari layar dan kosongkan papan klip secara manual saat selesai, terutama di komputer bersama.

Mengganti. Tombol «Rotate secret», dengan langkah konfirmasi. Ini tindakan yang langsung merusak: begitu rahasia baru ada, yang lama berhenti diverifikasi. Tidak ada jendela tumpang tindih — tidak ada momen kedua kunci sama-sama berlaku.

Yang rusak persis pengaitan pengunjung dengan pengguna Anda. Percakapan tidak terputus — pengunjung tetap menulis dan dijawab, hanya saja sebagai anonim — sampai backend Anda menandatangani dengan rahasia baru. Kode pemasangan dan daftar domain yang diizinkan tidak tersentuh; di situs Anda tidak ada yang perlu diubah.

Karena itu gantilah dengan sadar: siapkan dulu rilis backend dengan nilai baru, baru tekan «Rotate secret». Menekannya untuk melihat apa yang terjadi adalah ide buruk. Nilai baru ditampilkan di layar tepat setelah penggantian, jadi bisa langsung disalin.

Bagaimana widget berperilaku di halaman

Widget sengaja dibuat agar tidak berbenturan dengan situs Anda:

  • Ia hidup dalam kontainer terisolasi (Shadow DOM tertutup). Gaya Anda tidak masuk ke dalam widget dan gaya widget tidak bocor ke halaman. Efek sampingnya: Anda tidak bisa mengubah tampilannya dengan CSS sendiri — gunakan pengaturan tampilan.
  • Kontainer menutupi layar tetapi tidak menangkap klik: klik tembus ke halaman, hanya tombol dan panel chat yang merespons.
  • Widget selalu di atas konten Anda; lapisan Anda sendiri tidak bisa menutupinya.
  • Setiap galat di dalam widget tetap di dalam: paling buruk tombol chat tidak berfungsi, situs Anda tetap jalan. Jika widget tidak muncul, lihat konsol peramban — hampir selalu CSP atau domain yang belum ada di daftar.

Jika widget tidak muncul

Telusuri berurutan:

  1. Buka kode sumber halaman dan pastikan kedua tag ada dan kuncinya tidak kosong.
  2. Pastikan alamat situs persis sama dengan yang di daftar, termasuk https:// dan www.
  3. Lihat konsol peramban: pesan Content Security Policy berarti host platform harus diizinkan di CSP situs.
  4. Pastikan pemblokir iklan tidak menghapus widget — coba di jendela pribadi tanpa ekstensi.

Selanjutnya