Android SDK Implementation

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.

native-android-app

Secure visitor flow

Backend owns secrets, Android owns the experience.

SDK token
1

App requests token

2

Backend validates user

3

Ringnity returns SDK session

Chat
AI
Voice
Video

Ringnity Support

Online now

Hi, how can we help today?
I need help with my booking.
An agent can join by chat, voice, or video.
Type a message...

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

No Server API key
Uses short-lived SDK token

Customer Backend

Owned by the app developer

Validates app user
Stores Server API key

Ringnity Cloud

Tenant, chat, calls, AI, realtime

Issues SDK session
Processes SDK traffic
1. App requests token
2. Backend validates user
3. Backend calls Ringnity
4. SDK token returns
5. SDK opens chat/call

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

Preview

Launch a complete Ringnity support screen with one native Android view.

Headless Chat

Ready

Create conversations and messages from a native Android screen.

AI Chat

Progressive

Ask AI over HTTP and render the completed response through progressive chunk events.

Voice Call

Host integration

Creates call metadata and local media controls; the host must supply signalling and TURN.

Video Call

Host integration

Creates video metadata and local controls; end-to-end WebRTC signalling is not bundled.

Push and Ringtone

Ready

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.

text
: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 Scalar

Server

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

Ringnity tenant slug, for example your-slug.
Server API key created from the tenant dashboard.
Customer backend endpoint, for example POST /ringnity/runtime-token.
HTTPS in staging and production.
User validation in the customer backend before a token is issued.
No Server API key inside Android source, APK, logs, or remote config.

Android compatibility

Minimum Android SDK23
Compile SDK36
Java toolchain17
Kotlin2.2.0 in the preview Gradle project
Android Gradle Plugin8.11.1 in the preview Gradle project
NetworkHTTPS access to customer backend and Ringnity API
CallsRECORD_AUDIO, CAMERA, and runtime permission request
PushFCM token registration when background delivery is needed
Page size16 KB ready, no bundled native .so libraries in the current Android SDK package

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.

.env
RINGNITY_API_BASE_URL=https://api.ringnity.com
RINGNITY_SERVER_API_KEY=sk_live_or_test_from_ringnity_dashboard
server.js
import 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

json
{
  "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.

Server API key is stored only in backend environment variables or secret manager.
Backend verifies the customer user before issuing a token.
Token endpoint is protected by HTTPS, authentication, and rate limiting.
Logs do not print full runtime token, Server API key, or Ringnity credential values.
Android uses runtime permission prompts before microphone, camera, and notifications.
Production builds point to the production customer backend endpoint.

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 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 SDK
settings.gradle.kts
pluginManagement {
    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")
app/build.gradle.kts
dependencies {
    implementation(project(":ringnity-android-sdk"))

    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
}
AndroidManifest.xml
<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.

Kotlin
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.

Kotlin
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.

Kotlin
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.

Kotlin
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.

Kotlin
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.

Kotlin
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.

Kotlin
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.

Kotlin
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.

Kotlin
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.

Kotlin
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.

1

App opens the support entry point.

2

Android requests a runtime token from the customer backend.

3

Customer backend validates the app user and returns a short-lived context token.

4

Android creates the Ringnity SDK instance with tokenProvider.

5

Android chooses Managed UI or Headless UI.

6

Visitor conversation is created or restored.

7

App subscribes to updates while the screen is active.

8

Visitor sends chat, AI, voice, or video actions.

9

App cancels subscriptions when leaving the screen.

10

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.

Kotlin
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()
    }
}
Last updated: June 30, 2026

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>.