본문으로 건너뛰기

Android SDK

Android 앱에 ZeroTalk 채팅 위젯을 추가하는 방법을 안내합니다. 설치를 마치면 앱 위에 채팅 오버레이가 표시되며, 사용자는 네이티브 앱 경험을 유지하면서 상담을 시작할 수 있습니다.

사전 요구사항

항목요구사항
AndroidAPI 26 (Android 8.0) 이상
Kotlin1.8 이상
Plugin Key대시보드채팅 설정 → 연동 / 개발 → 연동 설정에서 발급
React Native · Flutter 는 아직 제공하지 않습니다

현재 공식 제공 SDK 는 iOS(Swift)Android(Kotlin) 두 가지입니다. React Native · Flutter 용 패키지는 배포하지 않습니다.

크로스플랫폼 앱이라면 각 플랫폼의 네이티브 모듈에서 위 두 SDK 를 감싸 연동해야 합니다. 필요하시면 문의해 주세요.

Plugin Key 위치

대시보드채팅 설정 → 연동 / 개발 → 연동 설정 → 웹사이트 카드 → Plugin Key 관리 → + 키 생성 에서 Plugin Key (pk_live_...) 를 발급받습니다.

설치

build.gradle (앱 모듈) 의 dependencies 에 다음을 추가합니다.

app/build.gradle.kts
dependencies {
implementation("ai.zeroworks:zerotalk-android-sdk:1.1.1")
}

Maven Central 은 기본 저장소에 포함되어 있습니다. 별도 저장소 설정 없이 바로 동기화합니다.

# Android Studio → Sync Project with Gradle Files
최신 버전 확인

Maven Central 에서 ai.zeroworks:zerotalk-android-sdk 의 최신 버전을 확인할 수 있습니다.

위젯 시작 (boot)

boot 는 앱 실행 시 한 번 호출합니다. 일반적으로 로그인 완료 후 또는 앱 홈 화면 진입 시점이 적합합니다.

import ai.zeroworks.zerotalk.BootStatus
import ai.zeroworks.zerotalk.ZeroTalk
import ai.zeroworks.zerotalk.ZeroTalkConfig

ZeroTalk.boot(
context = this,
config = ZeroTalkConfig(
pluginKey = "YOUR_PLUGIN_KEY",
apiBaseUrl = "https://api.talk.zeroworks.ai/api/v1",
wsUrl = "wss://api.talk.zeroworks.ai/ws/sdk"
)
) { status ->
when (status) {
is BootStatus.Success -> ZeroTalk.showMessenger(this)
is BootStatus.Failed -> Log.e("ZeroTalk", "boot 실패: ${status.error}")
}
}

boot 완료 전에 showMessenger() 를 호출해도 완료 후 자동으로 표시됩니다.

boot 중복 호출

boot 는 한 번만 호출합니다. 위젯을 재시작해야 하는 경우 shutdown() 호출 후 새 config 로 boot 를 재호출합니다. 단, 로그인·로그아웃에 따른 사용자 전환은 재부팅이 필요 없습니다identify() / logout() 을 사용하세요.

위젯 표시·숨김

ZeroTalk.showMessenger(context) // 위젯 열기
ZeroTalk.hideMessenger() // 위젯 닫기
ZeroTalk.toggle(context) // 열림 ↔ 닫힘 전환
ZeroTalk.shutdown() // 위젯 완전 종료 및 WebView 해제

상담창이 열려 있을 때 뒤로가기(하드웨어·제스처)를 누르면 상담창이 닫히고 onMessengerHide() 가 호출됩니다. 우측 상단 X 버튼과 같은 경로입니다. 상담창이 닫혀 있으면 뒤로가기는 평소대로 호스트 앱으로 전달됩니다.

호스트 앱이 추가로 할 일은 없습니다. Android 13 이상에서 예측형 뒤로가기(android:enableOnBackInvokedCallback="true")를 켠 앱도 그대로 동작하며, 메시지를 입력하는 중이라면 뒤로가기가 키보드를 먼저 내립니다.

1.0.6 이하

1.0.6 까지는 뒤로가기가 상담창 윈도우에서 소비되기만 하고 아무 일도 일어나지 않았습니다 — 상담창이 닫히지 않고 onMessengerHide() 도 호출되지 않으며, 호스트 앱의 백 핸들러까지 전달되지도 않았습니다. 런처 버튼 복구를 이 콜백에 의존한다면 1.0.7 이상이 필요합니다.

사용자 인증

로그인 사용자를 연동하면 상담 이력이 유지되고 사용자 정보가 대시보드에 표시됩니다.

ZeroTalk.boot(
context = this,
config = ZeroTalkConfig(
pluginKey = "YOUR_PLUGIN_KEY",
apiBaseUrl = "https://api.talk.zeroworks.ai/api/v1",
wsUrl = "wss://api.talk.zeroworks.ai/ws/sdk",
memberId = currentUser.id,
memberHash = computedHmac
)
)
memberHash 는 서버에서 계산합니다

memberHashSecret Key 로 계산한 HMAC-SHA256 서명입니다. 계산식: HMAC-SHA256(secretKey, memberId) — hex lowercase 결과값. secretKey 와 memberId 는 모두 UTF-8 인코딩된 바이트를 사용합니다. Secret Key 를 앱 코드에 포함하면 누구든 임의의 사용자로 위장할 수 있습니다. 반드시 자사 백엔드 API 를 통해 계산하여 앱에 전달합니다.

Secret Key 는 대시보드채팅 설정 → 연동 / 개발 → 연동 설정 → 웹사이트 카드 → Plugin Key 관리 에서 확인합니다. 생성 시 한 번만 표시되므로 반드시 기록해 두세요.

실행 중 사용자 전환

앱 실행 중 사용자가 로그인하거나 로그아웃하는 경우, 위젯을 재부팅하지 않고 전환합니다.

// 로그인 완료 시점
ZeroTalk.identify(
memberId = currentUser.id,
hash = computedHmac,
profile = mapOf("name" to currentUser.name),
customFields = mapOf("plan" to "premium")
)

// 로그아웃 시점 — 익명 사용자로 되돌아갑니다
ZeroTalk.logout()

profilecustomFields 는 선택 인자입니다. 두 메서드 모두 boot 이 완료된 뒤에만 동작합니다(부팅 전 호출은 무시됩니다).

고급 기능

이벤트 콜백

ZeroTalkListener 를 구현하면 위젯 상태 변화를 수신할 수 있습니다.

class MainActivity : AppCompatActivity(), ZeroTalkListener {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ZeroTalk.setListener(this)
}

override fun onMessengerShow() { }
override fun onMessengerHide() { }
override fun onBadgeChanged(unread: Int) { }
override fun onLinkClicked(url: String): Boolean { return false }
override fun onRouteRequested(path: String) { }
override fun onConnectionStatusChanged(status: ZeroTalkConnectionStatus) { }
override fun onError(error: ZeroTalkError) { }
override fun onPermissionRequested(type: String) { }
}

파일 첨부

위젯 내 파일 첨부 기능을 활성화하려면 Activity.onActivityResult 결과를 SDK 에 위임합니다.

MainActivity.kt
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (ZeroTalk.onActivityResult(requestCode, resultCode, data)) return
super.onActivityResult(requestCode, resultCode, data)
}
업로드 상한

첨부 파일은 300MB 까지 허용됩니다. 상한은 서버가 내려주는 값이라 워크스페이스 설정에 따라 달라질 수 있고, SDK 는 파일을 읽어들이기 전에 이 값으로 먼저 걸러냅니다. 대용량 파일은 앱이 백그라운드로 내려가도 전송이 이어집니다.

푸시 알림 (FCM)

FCM 토큰을 등록하면 앱이 백그라운드 상태일 때 새 메시지 알림을 받을 수 있습니다.

사전 준비 — 대시보드에 FCM 자격증명 등록 (관리자)

아래 앱 설정으로 토큰을 등록해도, 대시보드에 FCM 자격증명이 등록돼 있지 않으면 푸시가 발송되지 않습니다. 워크스페이스 관리자가 먼저 한 번 등록해야 합니다.

대시보드설정(⚙) → 연동 / 개발 → 모바일 SDK 푸시 페이지의 Android FCM 카드에서 다음을 업로드합니다.

입력 항목무엇을 / 어디서
Service Account JSONFirebase Console → 프로젝트 설정 → 서비스 계정새 비공개 키 생성 으로 다운로드한 JSON 파일 (FCM HTTP v1 방식, legacy server key 아님).

1단계: 앱에 Firebase 를 연결합니다.

SDK 는 firebase-messaging 을 포함하고 있지만, Firebase 프로젝트 연결은 호스트 앱의 몫입니다. 이 단계를 건너뛰면 FCM 토큰이 발급되지 않아 이후 단계가 전부 무효가 됩니다.

  1. Firebase Console 에서 앱의 패키지명으로 Android 앱을 등록하고 google-services.json 을 내려받아 app/ 에 넣습니다.
  2. Gradle 에 google-services 플러그인을 적용합니다.
app/build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.gms.google-services'
}

2단계: 토큰을 SDK 에 전달합니다.

1.1.0 부터 바뀝니다

1.0.7 까지는 SDK 가 FCM 수신 서비스를 자동으로 등록했습니다. 그런데 FCM 은 수신 서비스를 하나만 선택하므로, 앱이 자체 서비스를 갖고 있으면 둘 중 하나가 이벤트를 못 받습니다 — SDK 것이 선택되면 앱의 알림이 통째로 동작하지 않습니다.

1.1.0 부터 SDK 는 수신 서비스를 등록하지 않습니다. 아래 두 방법 중 하나를 반드시 적용해 주세요.

방법 A — 앱에 자체 FirebaseMessagingService 가 있는 경우 (권장)

앱 서비스에서 SDK 로 위임합니다.

MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {

override fun onNewToken(token: String) {
ZeroTalk.initPushToken(token) // ZeroTalk 로 위임
// ... 앱 자체 토큰 처리
}

override fun onMessageReceived(message: RemoteMessage) {
if (ZeroTalk.isZeroTalkPush(message.data)) return // 상담 알림은 SDK 담당
// ... 앱 자체 알림 처리
}
}

방법 B — 자체 서비스가 없는 경우 (코드 0줄)

SDK 가 제공하는 서비스를 앱 매니페스트에 직접 선언합니다.

AndroidManifest.xml
<service
android:name="ai.zeroworks.zerotalk.ZeroTalkMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

onNewToken 은 토큰이 이미 발급된 기기(재설치·재로그인 등)에서는 다시 호출되지 않습니다. 앱 시작 시 현재 토큰도 함께 전달하세요.

FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
ZeroTalk.initPushToken(token)
}

3단계: 알림 채널을 만듭니다 (Android 8.0 / API 26 이상 필수).

채널이 없으면 푸시가 도착해도 알림이 표시되지 않습니다. 채널 ID 는 매니페스트의 기본 채널 값과 일치해야 합니다.

MainActivity.kt
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"zerotalk_push_channel",
"ZeroTalk Push",
NotificationManager.IMPORTANCE_HIGH
)
getSystemService(NotificationManager::class.java)?.createNotificationChannel(channel)
}
AndroidManifest.xml
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="zerotalk_push_channel" />

4단계: 알림을 탭하면 그 대화가 열리도록 연결합니다 (1.1.0 이상).

알림 탭은 수신 서비스가 아니라 런처 Activity 로 전달됩니다. onCreateonNewIntent 양쪽에서 SDK 로 넘겨 주세요.

MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ZeroTalk.handlePushIntent(intent, this)
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent) // 빠뜨리면 두 번째 탭부터 이전 알림을 보게 됩니다
ZeroTalk.handlePushIntent(intent, this)
}

제로톡 알림이 아니면 false 를 돌려주므로, 앱 자체 알림 처리와 함께 써도 안전합니다.

상담창이 닫혀 있을 때

1.1.1 부터는 SDK 가 상담창까지 직접 띄우고 그 대화를 엽니다. 위 예시처럼 handlePushIntent 에 Activity 를 넘겨 주면 됩니다 — 호스트가 showMessenger() 를 따로 부를 필요가 없습니다.

앱이 완전히 종료된 상태에서는 알림 탭이 위젯 준비보다 먼저 도착하는데, SDK 가 그 요청을 들고 있다가 준비되는 즉시 처리합니다. 순서를 신경 쓸 필요는 없습니다.

로그아웃하거나 다른 계정으로 로그인하면 들고 있던 요청은 버립니다. 이전 사용자 앞으로 온 알림이 새 사용자 화면에서 열리지 않게 하기 위해서입니다.

알림 표시와 탭 수신 자체는 호스트 앱의 몫입니다 — SDK 는 제로톡 알림인지 판별하고 해당 대화를 여는 것을 담당합니다.

1.1.0 에서는 알림 탭이 동작하지 않습니다

1.1.0 은 대화 번호를 위젯에 넘기는 자리에 결함이 있어 알림을 탭해도 대화가 열리지 않습니다. 판별(isZeroTalkPush)은 정상입니다. 1.1.1 로 올려 주세요.

알림이 안 열릴 때 — 진단 로그를 켜세요 (1.1.1 이상)
ZeroTalk.setDebugLogging(true) // boot 전에 호출

logcat 에서 태그 ZeroTalkSDK 를 보면 어느 단계까지 갔는지 알 수 있습니다.

logcat 에 보이는 것
아무것도 안 보임탭이 SDK 까지 오지 않았습니다 — onNewIntent / setIntent 배선을 확인하세요
[Push] ignored제로톡 알림이 아니라고 판별했습니다 — 알림에 실린 값을 확인하세요
[Push] open queued아직 위젯이 준비되지 않아 들고 있습니다. 준비되면 자동으로 처리됩니다
[Push] open sent위젯에 전달했습니다 — 여기까지 왔는데 안 열리면 제보해 주세요

실패 신호(ignored·queued·dropped)는 이 스위치와 관계없이 항상 남습니다.

앱 내 푸시 알림 ON/OFF 토글 API 는 현재 SDK 에 노출되어 있지 않습니다. 사용자가 알림을 받지 않으려면 OS 알림 설정에서 앱 알림을 꺼야 합니다. 앱 내 토글은 향후 위젯 UI / 대시보드 설정에서 제공될 예정입니다.

비회원 푸시는 1.0.7 부터 동작합니다

푸시 토큰은 대화 상대(contact)에 묶여 저장되는데, 로그인하지 않은 방문자는 첫 문의를 보내기 전까지 contact 이 만들어지지 않습니다.

1.0.6 이하는 이 시점을 넘기면 토큰 등록을 다시 시도하지 않아 비회원 사용자에게 푸시가 도착하지 않았습니다. 앱에도 서버에도 오류가 남지 않아 증상만으로는 알아채기 어려웠습니다.

1.0.7 부터는 세션이 만들어질 때까지 토큰을 들고 있다가 자동으로 등록합니다. 호스트 앱이 추가로 할 일은 없습니다.

iOS 는 0.1.17 부터 동일하게 해소됐습니다.

Android 13 (API 33) 이상

POST_NOTIFICATIONS 권한이 필요합니다. AndroidManifest.xml<manifest> 직속에 <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> 를 선언하고, 호스트 앱에서 OS 권한 다이얼로그를 호출하여 사용자 허용을 받습니다.

테마

CSS 변수 토큰으로 위젯의 글꼴·모서리 등을 커스터마이징합니다.

// boot 시점에 적용
ZeroTalkConfig(
pluginKey = "YOUR_PLUGIN_KEY",
// ...
theme = mapOf(
"--zerotalk-font-family" to "'Pretendard', sans-serif",
"--zerotalk-radius" to "12px"
)
)

// 런타임에 변경
ZeroTalk.setTheme(mapOf("--zerotalk-radius" to "12px"))
색상(--zerotalk-primary 계열)은 여기서 지정하지 않습니다

브랜드 색상은 대시보드채팅 설정 → 연동 / 개발 의 위젯 색상 설정으로 관리됩니다. SDK 는 라이트/다크 모드를 적용할 때 색상 토큰을 자체 값으로 다시 쓰기 때문에, theme 이나 setTheme 으로 넘긴 --zerotalk-primary 계열 값은 유지되지 않습니다.

다크모드 (Appearance)

기기 OS 다크모드 설정에 따라 위젯 외관을 자동 전환합니다. 기본값은 SYSTEM 으로 OS 설정을 따릅니다.

ZeroTalkConfig(
pluginKey = "YOUR_PLUGIN_KEY",
// ...
appearance = Appearance.SYSTEM // SYSTEM | LIGHT | DARK
)
설명
SYSTEMOS 다크모드 설정을 따름 (기본값)
LIGHT항상 라이트 모드
DARK항상 다크 모드

Activity 에서 uiMode 구성 변경을 직접 처리하는 경우, AndroidManifest.xmlconfigChanges 를 선언한 뒤 onConfigurationChanged 에서 SDK 에 변경 사항을 전달합니다.

AndroidManifest.xml
<activity
android:name=".MainActivity"
android:configChanges="uiMode|screenSize|orientation" />
MainActivity.kt
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
ZeroTalk.onConfigurationChanged(newConfig)
}

사용자 프로필·커스텀 필드

대시보드에서 사용자 정보를 확인할 수 있도록 프로필 정보와 커스텀 속성을 전달합니다.

ZeroTalkConfig(
pluginKey = "YOUR_PLUGIN_KEY",
// ...
profile = mapOf(
"name" to "홍길동",
"email" to "hong@example.com",
"phone" to "010-1234-5678"
),
customFields = mapOf(
"plan" to "premium",
"company_id" to "company_123"
)
)

:::info customFields 키 명명 규칙

대시보드에 미리 등록된 커스텀 필드만 저장됩니다. 키는 정규식 ^[a-z][a-z0-9_]{0,99}$ 을 만족해야 합니다 — 소문자로 시작하고, 영문 소문자·숫자·_ 만 허용, 길이 1–100자.

  • plan, company_id, nested_plan
  • Plan (대문자 시작), companyId (camelCase), company.id (점 포함), 1plan (숫자 시작)

규칙 위반 키 또는 대시보드 미등록 키는 무시되거나 422 VALIDATION_FAILED 응답을 받을 수 있습니다.

:::

:::tip profile.phone 형식

profile.phone 은 E.164 정규화(예: +821012345678) 후 저장됩니다. 010-1234-5678 처럼 한국식으로 보내도 무방합니다. 이전 키 mobileNumber 는 서버에서 인식하지 않으므로 phone 으로 사용하세요.

:::

API 레퍼런스

ZeroTalkConfig

파라미터타입필수설명
pluginKeyString필수대시보드에서 발급한 Plugin Key (pk_live_ 또는 pk_test_ 로 시작)
apiBaseUrlString필수https://api.talk.zeroworks.ai/api/v1
wsUrlString필수wss://api.talk.zeroworks.ai/ws/sdk
cdnBaseUrlString?선택위젯 리소스 CDN. 기본값 https://cdn.talk.zeroworks.ai/latest — 운영 환경이면 생략합니다
memberIdString?선택로그인 사용자 ID. 비회원이면 생략
memberHashString?선택HMAC-SHA256 서명. 회원 인증 시 필수
languageZeroTalkLanguage?선택위젯 표시 언어. 생략 시 ko
bootTimeoutSecondsDouble?선택boot 완료 대기 한도(초). 기본값 15. 0 이하·3600 초과는 설정 오류
profileMap<String, Any>?선택사용자 이름·이메일 등 프로필 정보
customFieldsMap<String, Any>?선택커스텀 속성 (플랜, 회사 ID 등)
themeMap<String, String>?선택CSS 변수 토큰 (글꼴·모서리 등. 색상은 대시보드에서 관리)
appearanceAppearance선택위젯 외관 모드. 기본값 SYSTEM

ZeroTalkLanguage

설명
AUTO기기 언어 자동 감지 (인식 못 하면 ko)
KO · EN · ZH_CN · ZH_TW · JA해당 언어로 고정

Appearance

설명
SYSTEMOS 다크모드 설정을 따름 (기본값)
LIGHT항상 라이트 모드
DARK항상 다크 모드

ZeroTalkListener

메서드설명
onMessengerShow()위젯이 열렸을 때
onMessengerHide()위젯이 닫혔을 때. X 버튼, hideMessenger(), 뒤로가기(1.0.7+) 모두 이 콜백으로 모입니다
onBadgeChanged(unread: Int)읽지 않은 메시지 수 변경
onLinkClicked(url: String): Boolean링크 클릭. false (기본) — SDK 가 확인 다이얼로그 후 앱 내 브라우저로 열기. true — SDK 는 열지 않고 호스트 앱이 자체 처리
onRouteRequested(path: String)앱 내 화면 이동 요청
onConnectionStatusChanged(status: ZeroTalkConnectionStatus)WebSocket 연결 상태 변경
onError(error: ZeroTalkError)오류 발생
onPermissionRequested(type: String)위젯이 OS 권한을 필요로 할 때 호출. 호스트 앱이 권한을 요청한 뒤 notifyPermission(type, granted) 로 결과를 알려줍니다

ZeroTalkConnectionStatus

설명
CONNECTEDWebSocket 연결됨
DISCONNECTEDWebSocket 연결 끊김
CONNECTING연결 시도 중

정적 메서드

메서드설명
boot(context, config) { status -> }위젯 초기화
showMessenger(context)위젯 열기
hideMessenger()위젯 닫기
toggle(context)열림 ↔ 닫힘 전환
shutdown()위젯 종료 및 WebView 해제
setListener(listener)이벤트 콜백 수신 객체 설정
identify(memberId, hash, profile?, customFields?)실행 중 로그인 사용자로 전환 (재부팅 불필요)
logout()익명 사용자로 되돌리기
initPushToken(token: String)FCM 토큰 등록
isZeroTalkPush(data: Map<String, String>): Boolean받은 알림이 제로톡 상담 알림인지 판별 (1.1.0 이상)
handlePushIntent(intent, activity): Boolean알림 탭을 넘겨 해당 대화를 엶. 제로톡 알림이 아니면 false (1.1.0 이상, 정상 동작은 1.1.1 이상)
setDebugLogging(enabled: Boolean)진단 로그 켜기 (1.1.1 이상). 아래 참고
notifyPermission(type: String, granted: Boolean)onPermissionRequested 로 요청받은 OS 권한의 사용자 응답을 위젯에 통보. "push" 토글 용도로는 사용하지 않습니다.
setTheme(tokens: Map<String, String>)런타임 테마 변경 (색상 토큰 제외)
onActivityResult(requestCode, resultCode, data): Boolean파일 첨부용 Activity 결과 위임. true 반환 시 SDK 가 처리
onConfigurationChanged(newConfig: Configuration)다크모드 즉시 반영

ZeroTalkError

케이스설명
NotBootedYetboot 호출 전에 다른 메서드를 호출했을 때
BootInProgressboot 가 아직 진행 중일 때 중복 호출
InvalidConfigpluginKey 등 필수 설정값이 유효하지 않을 때
LoadFailed위젯 리소스 로드 실패
BootTimeoutboot 완료 타임아웃
BridgeError(code, message)위젯 내부 오류

다음 단계