Integrasi Android
Tampilkan Webchat di aplikasi Android lewat WebView, dengan data user yang login langsung terisi dan riwayat chat yang ikut pindah perangkat.
Webchat bisa dibuka layar penuh di dalam aplikasi Android lewat WebView. Aplikasi mengirim nama dan nomor HP user yang login, sehingga form pra-chat dilewati. Contoh di halaman ini memakai Kotlin.
URL chat
Ambil ID channel di Integrasi → Webchat → ikon edit → bagian Pasang. Buka URL berikut di WebView:
https://app.csku.ai/webchat/<ID_CHANNEL>?embed=app
| Parameter | Wajib | Keterangan |
|---|---|---|
embed=app | Ya | Mode aplikasi: layar penuh, langsung terbuka, menerima pesan dari aplikasi. |
header=0 | Tidak | Menyembunyikan header chat (nama, logo, tombol riwayat, tombol tutup). Pakai jika aplikasi punya toolbar sendiri. |
name, phone, email | Tidak | Data user yang login, aturannya sama dengan identify. Jika name dan phone valid, form pra-chat dilewati. Data tidak valid diabaikan. |
autostart=1 | Tidak | Sesi chat langsung dibuat saat halaman dibuka, tidak menunggu pesan pertama. Event session langsung terkirim. |
Parameter header, name, phone, email, dan autostart hanya berlaku bersama embed=app, dan nilainya harus persis seperti di tabel.
Cara tercepat: buka URL dengan data user, tanpa kode tambahan.
val url = Uri.parse("https://app.csku.ai/webchat/$CHANNEL_KEY").buildUpon()
.appendQueryParameter("embed", "app")
.appendQueryParameter("autostart", "1")
.appendQueryParameter("name", user.name)
.appendQueryParameter("phone", user.phone)
.appendQueryParameter("email", user.email ?: "")
.build()
webView.loadUrl(url.toString())
Data di URL tercatat
Parameter URL tersimpan di riwayat dan log server seperti URL lainnya. Token chat tidak bisa dikirim lewat URL; kirim lewat identify.
Setup WebView
Aktifkan JavaScript dan DOM storage (sesi chat disimpan di localStorage), daftarkan bridge bernama CskuWebchatBridge, lalu muat URL chat.
class SupportChatActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
webView = WebView(this)
setContentView(webView)
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
}
webView.addJavascriptInterface(ChatBridge(), "CskuWebchatBridge")
webView.webViewClient = ChatWebViewClient()
webView.webChromeClient = ChatChromeClient(this)
webView.loadUrl("https://app.csku.ai/webchat/$CHANNEL_KEY?embed=app")
}
// Tombol back menutup chat, bukan halaman sebelumnya di WebView
@Deprecated("Deprecated in Java")
override fun onBackPressed() = finish()
}
Data user (identify)
Kirim data user setelah onPageFinished. Jika data tiba sebelum chat selesai dimuat, datanya tetap dipakai.
private inner class ChatWebViewClient : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
val user = Session.currentUser ?: return
val payload = JSONObject()
.put("name", user.name)
.put("phone", user.phone)
.put("email", user.email ?: "")
// Token chat dari server Anda (event session); lewati jika belum ada
user.chatToken?.let { payload.put("token", it) }
postToChat(view, "identify", payload)
}
// Link di dalam pesan dibuka di browser, bukan di WebView chat
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
val url = request.url
if (url.host == "app.csku.ai") return false
startActivity(Intent(Intent.ACTION_VIEW, url))
return true
}
}
fun postToChat(view: WebView, type: String, payload: JSONObject? = null) {
val msg = JSONObject().put("source", "csku-webchat").put("type", type)
if (payload != null) msg.put("payload", payload)
view.evaluateJavascript("window.postMessage($msg, '*')", null)
}
| Field | Wajib | Aturan |
|---|---|---|
name | Ya | 2–60 karakter. |
phone | Ya | 8–15 digit, boleh diawali +. Spasi, -, (, ), dan . dibuang dulu. Contoh: 081234567890, +6281234567890. |
email | Tidak | Format email valid, maksimal 120 karakter. |
token | Tidak | Token chat dari event session. 16–128 karakter: huruf, angka, _, -. Token salah format diabaikan. |
- Data valid: form pra-chat dilewati, dan tim CS melihat nama serta nomor ini di menu Chat.
- Data tidak valid: diabaikan, user melihat form pra-chat seperti biasa.
- Hanya
identifyvalid pertama di setiap pemuatan halaman yang dipakai. - Tanpa
token: jika perangkat ini sudah pernah chat, profil yang tersimpan tidak ditimpa. - Dengan
token: chat di perangkat ini diganti dengan chat milik token tersebut, termasuk riwayatnya.
Pesan dari aplikasi ke chat
Semua dikirim lewat postToChat(webView, type) dengan format {"source":"csku-webchat","type":"...","payload":{...}}.
type | Payload | Fungsi |
|---|---|---|
identify | { name, phone, email, token } | Mengisi data user. |
open | - | Menandai chat sedang dilihat. Jumlah belum dibaca direset ke 0. |
close | - | Menandai chat tidak sedang dilihat. Pesan baru dihitung belum dibaca. |
history | - | Membuka daftar riwayat percakapan. Hanya berlaku jika user punya lebih dari satu percakapan. Pakai dari toolbar aplikasi saat header=0. |
WebView di background
Jika WebView chat disimpan di background (misalnya di bottom sheet), kirim close saat disembunyikan dan open saat ditampilkan lagi, supaya badge belum dibaca akurat.
Event dari chat ke aplikasi
Chat memanggil CskuWebchatBridge.postMessage(json) dengan string JSON {"source":"csku-webchat","type":"...","payload":{...}}.
private inner class ChatBridge {
@JavascriptInterface
fun postMessage(json: String) {
val msg = runCatching { JSONObject(json) }.getOrNull() ?: return
if (msg.optString("source") != "csku-webchat") return
runOnUiThread {
when (msg.optString("type")) {
"ready" -> { /* halaman chat siap */ }
"unread" -> updateBadge(msg.optJSONObject("payload")?.optInt("count") ?: 0)
"close" -> finish() // user menekan tombol tutup di header chat
"session" -> msg.optJSONObject("payload")?.optString("token")
?.takeIf { it.isNotEmpty() }
?.let { saveChatTokenToServer(it) }
}
}
}
}
type | Payload | Kapan dikirim |
|---|---|---|
ready | - | Halaman chat sudah dimuat. |
unread | { count } (0–99) | Jumlah belum dibaca berubah. Bertambah hanya saat chat berstatus close. |
close | - | User menekan tombol tutup di header, atau Escape di keyboard fisik. Aplikasi yang memutuskan apa yang ditutup. |
session | { token } | Setiap kali sesi chat dibuka atau dibuat. Hanya dikirim di mode aplikasi. |
Selain session, event tidak pernah membawa data user atau isi pesan. Tanpa bridge, chat tetap berjalan, tetapi tombol tutup di header tidak berfungsi. Daftarkan bridge atau pakai header=0.
Token sesi
Token dari event session mengikat percakapan ke user. Simpan di server Anda untuk user yang login, lalu kirim lagi lewat identify.token. Dengan begitu riwayat chat user tetap muncul setelah instal ulang atau di HP lain.
- Terima event
session, kirim token ke server Anda bersama ID user yang login. - Saat user membuka chat, ambil token user dari server Anda.
- Kirim token di field
tokenpadaidentify.
Tanpa token, user yang sama di HP lain atau setelah instal ulang tercatat sebagai pengunjung baru.
Perlakukan token seperti password
Siapa pun yang memegang token bisa membaca dan mengirim pesan di percakapan user tersebut. Simpan hanya di server Anda, berikan hanya ke user yang login sebagai pemiliknya, dan jangan pernah menulisnya ke log.
Kirim gambar dan PDF
User bisa mengirim JPG, PNG, WEBP, GIF, dan PDF hingga 5 MB. WebView butuh WebChromeClient yang membuka pemilih file:
class ChatChromeClient(private val activity: ComponentActivity) : WebChromeClient() {
private var callback: ValueCallback<Array<Uri>>? = null
private val picker = activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
callback?.onReceiveValue(FileChooserParams.parseResult(result.resultCode, result.data))
callback = null
}
override fun onShowFileChooser(
view: WebView, filePathCallback: ValueCallback<Array<Uri>>, params: FileChooserParams
): Boolean {
callback?.onReceiveValue(null)
callback = filePathCallback
picker.launch(params.createIntent())
return true
}
}
Buat ChatChromeClient di onCreate, sebelum Activity STARTED, karena memakai registerForActivityResult.
Logout dan ganti akun
Sesi chat disimpan di penyimpanan WebView dan berlaku untuk seluruh aplikasi. Jika user A logout lalu user B login di HP yang sama tanpa membersihkan penyimpanan, B melihat percakapan A. Saat logout, panggil:
WebStorage.getInstance().deleteAllData()
CookieManager.getInstance().removeAllCookies(null)
Jika WebView juga dipakai untuk halaman lain yang datanya tidak boleh ikut terhapus, jalankan chat di proses terpisah dengan WebView.setDataDirectorySuffix("csku-chat") (Android 9+), lalu hapus direktori data itu saat logout.
Layar penuh (edge-to-edge)
Halaman chat otomatis memberi jarak untuk status bar, navigation bar, dan notch. Beberapa versi WebView lama tidak meneruskan jarak ini. Jika header tertutup status bar atau kotak ketik tertutup navigation bar (Android 15 / targetSdk 35 memaksa edge-to-edge), beri padding di native:
ViewCompat.setOnApplyWindowInsetsListener(webView) { v, insets ->
val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime())
v.setPadding(bars.left, bars.top, bars.right, bars.bottom)
WindowInsetsCompat.CONSUMED
}
Padding ini tidak membuat jarak dobel. Pakai android:windowSoftInputMode="adjustResize" pada Activity supaya kotak ketik tetap terlihat saat keyboard muncul.
Keamanan dan batasan
Identitas tidak diverifikasi
Nama dan nomor dari aplikasi tidak diverifikasi oleh CSKU. Siapa pun yang membuka URL chat bisa mengirim nomor lain. Gunakan sebagai info bantu untuk tim CS, bukan bukti identitas. Jangan membuka data sensitif (saldo, pesanan, data akun) hanya berdasarkan nomor yang tampil di chat.
- Pengunjung baru melewati pemeriksaan anti-spam otomatis saat memulai chat. WebView harus bisa memuat
challenges.cloudflare.com. - Satu pesan maksimal 2000 karakter.
- Tampilan chat berbahasa Indonesia dan bertema terang.
Checklist uji
- Login, buka chat: form pra-chat tidak muncul. Kirim pesan, cek nama dan nomor di menu Chat dashboard.
- Nomor salah format (misalnya
12345): form pra-chat muncul. - Tutup chat lalu buka lagi: riwayat pesan masih ada.
- Kirim
close, lalu balas dari dashboard: eventunreadmasuk dengancountbertambah. - Tombol tutup di header menutup Activity, atau
header=0dan toolbar aplikasi tampil. - Kirim gambar dan PDF.
- Ketuk link di pesan: terbuka di browser.
- Logout lalu login akun lain: chat mulai kosong.
- Event
sessionmasuk dan token tersimpan di server. Login akun yang sama di HP lain: riwayat chat muncul. - HP dengan gesture navigation dan notch: header dan kotak ketik tidak tertutup.
Troubleshooting
| Gejala | Solusi |
|---|---|
| Form pra-chat tetap muncul | Pastikan URL memakai ?embed=app. Cek nama minimal 2 karakter dan nomor 8–15 digit. Kirim identify di onPageFinished. |
| Nama lama tampil setelah ganti akun | Hapus penyimpanan WebView saat logout. |
| Tombol tutup tidak berfungsi | Daftarkan CskuWebchatBridge atau pakai header=0. |
| Tombol lampiran tidak membuka galeri | Implementasikan onShowFileChooser. |
| Muncul "Verifikasi keamanan gagal" | Izinkan challenges.cloudflare.com di jaringan atau network security config. |
| Halaman kosong | Aktifkan JavaScript dan DOM storage. Cek ID channel di dashboard. |