Build visitor and agent communications inside an Android app
This guide covers the visitor runtime and the separate native Agent runtime. Each uses its own short-lived token, state, and lifecycle.
Use the Kotlin SDK for native Android support, marketplace, banking, hotel, travel, tourism, or customer apps. It includes an Easy/Managed UI preview for fast installs and a Headless object model for custom screens while platform-specific transport details stay internal.
Secure visitor flow
Backend owns secrets, Android owns the experience.
App requests token
Backend validates user
Ringnity returns SDK session
Ringnity Support
Online now
Overview
What this SDK is for
Use this Android SDK for customer-facing support experiences or internal agent workspaces. Visitor and Agent runtimes remain separate.
Visitor / Client App
This page covers customer-facing Android apps: chat, AI chat, internet voice call, video call, push notification, ringtone, Managed UI, and Headless UI.
Customer Backend Required
The Android app asks the customer backend for a short-lived SDK token. The backend stores the Ringnity Server API key and validates the app user.
Agent Runtime Included
RingnityAgent provides inbox, presence, direct and group Team chat, call-room control, push, and signaling. RingnityAndroidCallRoomSession joins Ringnity-managed group media while the host owns the native UI; admin billing and reports remain separate.
Server and SDK topology
Architecture that must be set up
The SDK is embedded in the Android app, but secret-bearing work belongs to the customer backend. This prevents developers from shipping the Ringnity Server API key inside the APK.
Android App
Customer app with Ringnity SDK
Customer Backend
Owned by the app developer
Ringnity Cloud
Tenant, chat, calls, AI, realtime
Do not call Ringnity Server API directly from Android
Android may call Ringnity runtime endpoints with a short-lived SDK session token. It must not contain the Server API key, platform owner credentials, or long-lived admin credentials.
Overview
Visitor capability map
This map covers the visitor runtime. Agent data and signaling APIs use RingnityAgent and the canonical Agent SDK contract.
Managed Support UI
Launch a complete Ringnity support screen with one native Android view.
Headless Chat
Create conversations and messages from a native Android screen.
AI Chat
Ask AI over HTTP and render the completed response through progressive chunk events.
Voice Call
Creates call metadata and local media controls; the host must supply signalling and TURN.
Video Call
Creates video metadata and local controls; end-to-end WebRTC signalling is not bundled.
Push and Ringtone
Bring visitors back when an agent replies or a call is waiting.
Package shape
The Android preview source package contains two Gradle modules. Most Android visitor apps include the Android module because it depends on the core SDK.
:ringnity-sdk
Core Kotlin/JVM object model, HTTP adapter, generated operation layer,
coroutines, and Flow APIs.
:ringnity-android-sdk
Android managed views, call adapter, notification/ringtone adapter,
and Socket.IO realtime transport.Scalar API Reference
Use Scalar when backend developers need exact endpoint fields, schemas, auth requirements, status codes, or generated client details.
Open ScalarServer
Server requirements
The backend endpoint is the trust boundary. It authenticates the customer app user, creates Ringnity context, exchanges it to a SDK session token, and returns only that short-lived token to Android.
Backend checklist
Android compatibility
Server
Create the runtime token endpoint
Keep secrets on your backend while Android receives only short-lived access. The example below creates a trusted visitor context, exchanges it for a SDK session, then returns the SDK session token.
RINGNITY_API_BASE_URL=https://api.ringnity.com
RINGNITY_SERVER_API_KEY=sk_live_or_test_from_ringnity_dashboardimport express from "express";
import { RingnityServerApi } from "@ringnity/server-api";
const app = express();
app.use(express.json());
const ringnity = RingnityServerApi.create({
apiBaseUrl: process.env.RINGNITY_API_BASE_URL,
apiKey: process.env.RINGNITY_SERVER_API_KEY,
});
async function requireCustomer(req) {
// Replace this with your own session, JWT, or account validation.
return {
id: req.body.userId,
name: req.body.name,
email: req.body.email,
};
}
app.post("/ringnity/runtime-token", async (req, res) => {
const customer = await requireCustomer(req);
const context = await ringnity.tokens.visitorContext({
visitor: {
externalId: customer.id,
name: customer.name,
email: customer.email,
},
metadata: {
source: "android-app",
appVersion: req.body.appVersion,
},
expiresIn: 900,
});
res.json({
token: context.contextToken,
expiresIn: context.expiresIn,
tokenType: context.tokenType,
});
});
app.listen(3000);Android receives
{
"token": "eyJhbGciOi...",
"expiresIn": 3600,
"tokenType": "Bearer"
}Contract
Keep the mobile response small: token, expiresIn, and tokenType. Avoid returning the Server API key, tenant internal id, credential id, scopes, or debug payloads to Android.
Server
Token lifecycle
Move from app entry to support session with a predictable flow. Android can ask the backend for a new token when the app starts, when the SDK returns unauthorized, or before a long chat/call session.
Create
Backend validates the app user and returns a short-lived SDK token.
Use
SDK sends the token as Ringnity SDK session authorization for chat, calls, AI, and push registration.
Refresh
When expired, request a new token from the customer backend and recreate or refresh the SDK client.
Server
Security checklist
Use this checklist before sharing the Android build with a real customer.
Android Setup
Download what you need
Current downloadable SDK bundle: 0.2.0-beta.0. Use the Android Kotlin SDK for the app and the Server API SDK for the customer backend token endpoint.
Android Kotlin SDK
Core Kotlin SDK plus Android managed UI, call adapter, notifications, and realtime transport.
Server API SDK
Backend helper package for token exchange. Keep the Server API key inside this backend only.
OpenAPI Contract
Use the live contract for exact REST schemas, fields, and status codes.
Scalar Reference
Interactive API reference for backend developers and QA teams.
Android Setup
Install the Kotlin Android SDK
During preview, download the source package and include it as local Gradle modules. When public Maven publishing is enabled, this section can switch to Maven coordinates.
1. Download and extract
Download the Android Kotlin SDK zip, extract it beside your Android app workspace, then wire both Gradle modules.
Download Android Kotlin SDKpluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
include(":app")
include(":ringnity-sdk")
include(":ringnity-android-sdk")
project(":ringnity-sdk").projectDir =
file("../ringnity-kotlin-sdk/ringnity-sdk")
project(":ringnity-android-sdk").projectDir =
file("../ringnity-kotlin-sdk/ringnity-android-sdk")dependencies {
implementation(project(":ringnity-android-sdk"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
}<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />Android Setup
Initialize SDK
Create the SDK from an Activity or Fragment lifecycle coroutine. The token provider calls the customer backend endpoint from the Server section.
import com.ringnity.sdk.Ringnity
import com.ringnity.sdk.RingnityCallMode
import com.ringnity.sdk.RingnityConfig
import com.ringnity.sdk.RingnityRuntimeToken
import com.ringnity.sdk.android.RingnityAndroidCallAdapter
import com.ringnity.sdk.android.RingnitySocketIoRealtimeTransport
suspend fun fetchRingnityRuntimeToken(): RingnityRuntimeToken {
val response = customerBackend.post("/ringnity/runtime-token")
return RingnityRuntimeToken(
token = response.token,
expiresIn = response.expiresIn,
tokenType = response.tokenType ?: "Bearer",
)
}
val callAdapter = RingnityAndroidCallAdapter(applicationContext)
val ringnity = Ringnity.create(
RingnityConfig(
tokenProvider = ::fetchRingnityRuntimeToken,
callMode = RingnityCallMode.BASIC,
mediaAdapter = callAdapter,
notificationAdapter = callAdapter,
realtimeTransport = RingnitySocketIoRealtimeTransport(),
),
)Managed UI
Managed UI overview
Launch a complete Ringnity support screen with one native view. Use Managed UI when the Android app wants Ringnity to draw the support screen.
import com.ringnity.sdk.android.RingnityAndroidManagedView
val supportView = RingnityAndroidManagedView(this)
// Optional when the host app owns native video surfaces.
supportView.setRemoteVideoView(remoteVideoView)
supportView.setLocalPreviewView(localPreviewView)
supportView.bind(
ringnity = ringnity,
conversationSubject = "Android customer support",
)
setContentView(supportView)Video view injection
If the host app already owns native video surfaces, inject remote and local preview views before binding the managed support view. Otherwise, use the default managed call surface.
Managed UI
Included features
With Managed UI, the feature implementation is bundled into RingnityAndroidManagedView. The host app still owns backend token flow, Android permissions, FCM setup, and app lifecycle.
Chat
Managed support screen renders the visitor conversation, message list, and basic chat state.
AI Chat
AI surface is available when the tenant plan and AI readiness allow it.
Voice Call
Managed preview exposes local controls after the host supplies signalling and remote media.
Video Call
Managed preview exposes video controls after the host supplies signalling, TURN, and remote media.
Push Notification
Host app still registers FCM; managed experiences can benefit from the same device registration.
Ringtone
Managed foreground call experience can use the configured notification adapter.
Modular managed views
Use modular managed views when the app owns navigation but still wants Ringnity to render one visitor support surface, such as only Chat, AI, or Call.
val chatView = RingnityAndroidChatView(this)
chatView.bind(
ringnity = ringnity,
conversationSubject = "Android customer support",
)
val aiView = RingnityAndroidAiView(this)
aiView.bind(ringnity)
val callView = RingnityAndroidCallView(this)
callView.setRemoteVideoView(remoteVideoView)
callView.setLocalPreviewView(localPreviewView)
callView.bind(
ringnity = ringnity,
conversationSubject = "Android customer support",
)Headless UI
Headless UI overview
Use Ringnity as the support engine behind your own Android screens. Headless UI is for apps that own navigation, message rendering, call controls, and analytics.
You own UI
Render message bubbles, composer, loading states, call buttons, AI response UI, and empty states in your app design.
SDK owns product logic
Use Ringnity for runtime token usage, conversation creation, message send, subscribe, AI, calls, push token, and ringtone operations.
Headless UI
Feature APIs
Build your own support experience while Ringnity handles sessions and events. The sections below are the manual APIs used when the Android app renders its own visitor UI.
Headless code starts here
Use these APIs only when your app owns the UI for chat, AI, calls, push behavior, and ringtone lifecycle.
Headless UI Feature API
Chat
Create conversations and messages from a native Android screen. Managed UI includes the chat screen; Headless UI requires the app to render chat and call the SDK methods.
Managed UI
Included in RingnityAndroidManagedView. The managed support screen creates/uses the conversation, renders messages, and handles basic chat state for the visitor.
Headless UI
Use these APIs when your app owns message bubbles, composer, loading state, and conversation rendering.
val readiness = ringnity.account.readiness()
val conversation = ringnity.chat.createConversation(
subject = "Need help with my order",
)
val conversationId = conversation["conversationId"] as String
val message = ringnity.chat.sendMessage(
conversationId = conversationId,
body = "Hi, I need help.",
)
ringnity.chat.markRead(
conversationId = conversationId,
messageId = message["messageId"] as String,
)
// Also available: availability, activeConversation, getConversation,
// closeConversation, updateProfile, editMessage, deleteMessage, createAttachment,
// uploadAttachment, requestCall, and finalizeCallRequest. AI also exposes routing and searchKnowledge.Managed UI
Included as part of the managed chat surface while the Ringnity view is active.
Headless UI
Subscribe while the custom chat screen is open, then cancel the coroutine job when the screen closes.
val job = lifecycleScope.launch {
ringnity.chat.subscribeEvents(
conversationId = conversationId,
).collect { event ->
when (event.type) {
"conversation.updated" -> renderConversation(event.data)
"message.created" -> appendMessage(event.data)
else -> logRingnityEvent(event)
}
}
}
// Call this when the screen is destroyed or the conversation is closed.
job.cancel()Headless UI Feature API
AI Chat
AI currently returns one HTTP response; the SDK emits progressive chunks afterward for rendering. This is not transport streaming.
Managed UI
Included in the managed support screen when AI Chat is enabled for the tenant and plan contract.
Headless UI
Use this API when your app owns the AI prompt field, response bubble, typing state, and error display.
ringnity.ai.streamChat(
message = "Please help me choose the right plan.",
conversationId = conversationId,
).collect { event ->
when (event["type"]) {
"ai.message.delta" -> appendAiDelta(event)
"ai.message.completed" -> finishAiMessage(event)
"ai.message.failed" -> showAiError(event)
}
}Headless UI Feature API
Voice Call
Create voice call metadata and local controls. Production audio still requires host-provided signalling, ICE/TURN, and remote media wiring.
Managed UI
Included in the managed call surface when Voice Call is enabled. The managed view presents the visitor-facing call action and call state.
Headless UI
Use this API only with a host signalling transport; the bundled adapter controls local media but does not negotiate the call.
val permissionState = callAdapter.permissions(video = false)
if (permissionState.canStartAudio) {
val call = ringnity.calls.startAudio(
conversationId = conversationId,
)
call.mute()
call.unmute()
call.end()
}Headless UI Feature API
Video Call
Create video call metadata and local controls. Production video still requires host-provided signalling, ICE/TURN, and remote media wiring.
Managed UI
Included in the managed call surface when Video Call is enabled. Apps may inject native video surfaces when they need custom rendering.
Headless UI
Use this API only with host signalling and TURN; the bundled adapter controls local camera and views.
val permissionState = callAdapter.permissions(video = true)
if (permissionState.canStartVideo) {
val call = ringnity.calls.startVideo(
conversationId = conversationId,
)
call.setVideoEnabled(false)
call.setVideoEnabled(true)
call.end()
}Headless UI Feature API
Push Notification
Bring visitors back when an agent replies or a call is waiting. Managed UI can benefit from it, but the app still owns FCM setup and token registration.
Managed UI
Required for reliable background wake-up around managed chat/call experiences. The host Android app still owns Firebase setup and notification channel behavior.
Headless UI
Use the same registration API when your custom UI needs background chat or call wake-up behavior.
ringnity.devices.registerPushToken(
platform = RingnityPushPlatform.ANDROID,
provider = RingnityPushProvider.FCM,
token = firebaseMessagingToken,
audience = RingnityPushAudience.CUSTOMER,
externalId = "customer-123",
appId = "com.example.customer",
)Headless UI Feature API
Ringtone
Make incoming calls feel immediate while the app is open. Managed UI can use the notification adapter; Headless UI can call ringtone methods around custom call screens.
Managed UI
Available through the configured notification adapter for foreground managed call experiences. Background ringing still belongs to FCM and the host notification channel.
Headless UI
Use these helpers when your app owns the incoming call popup, accept/decline buttons, and active call lifecycle.
ringnity.notifications.previewRingtone()
ringnity.notifications.playIncomingRingtone()
// Call this when the visitor answers, declines, or the incoming call expires.
ringnity.notifications.stopRingtone()Operations
Recommended lifecycle
Treat support as a native app lifecycle, not a one-off API call. This is the normal runtime sequence for a visitor support entry point in an Android app.
App opens the support entry point.
Android requests a runtime token from the customer backend.
Customer backend validates the app user and returns a short-lived context token.
Android creates the Ringnity SDK instance with tokenProvider.
Android chooses Managed UI or Headless UI.
Visitor conversation is created or restored.
App subscribes to updates while the screen is active.
Visitor sends chat, AI, voice, or video actions.
App cancels subscriptions when leaving the screen.
App asks the backend for a new token when the SDK session expires.
Operations
Troubleshooting
Most failures come from a missing tenant, expired token, wrong token type, or permission flow.
SDK_CLIENT_NOT_FOUND
Check the tenant slug used by the customer backend when it calls /api/sdk/session.
SDK_ORIGIN_NOT_ALLOWED
For browser/web this is usually allowed domain. For Android backend-to-Ringnity exchange, confirm the backend is not forwarding an invalid Origin header.
SDK_CONTEXT_TOKEN_INVALID
Create a fresh visitor context token from the Server API, then exchange it immediately.
SDK_SESSION_REQUIRED
The Android SDK is calling a runtime endpoint without a valid SDK session token.
Microphone or camera blocked
Request runtime permissions before starting audio or video.
Operations
Full Activity example
This sample shows the basic lifecycle shape: create SDK, create conversation, subscribe, send first message, then cancel subscription.
class SupportActivity : AppCompatActivity() {
private lateinit var ringnity: Ringnity
private lateinit var callAdapter: RingnityAndroidCallAdapter
private var subscriptionJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
callAdapter = RingnityAndroidCallAdapter(applicationContext)
ringnity = Ringnity.create(
RingnityConfig(
tokenProvider = ::fetchRingnityRuntimeToken,
callMode = RingnityCallMode.BASIC,
mediaAdapter = callAdapter,
notificationAdapter = callAdapter,
realtimeTransport = RingnitySocketIoRealtimeTransport(),
),
)
val conversation = ringnity.chat.createConversation(
subject = "Android support",
)
val conversationId = conversation["conversationId"] as String
subscriptionJob = launch {
ringnity.chat.subscribeEvents(conversationId).collect { event ->
renderRingnityEvent(event)
}
}
ringnity.chat.sendMessage(
conversationId = conversationId,
body = "Hi, I need help.",
)
}
}
override fun onDestroy() {
subscriptionJob?.cancel()
super.onDestroy()
}
}Turn Your Website Into a Real-Time Call Center
Let customers call your team directly from your website, no phone numbers and no apps required. Just add one <script>.
