Build visitor and agent communications inside a React Native app
This guide covers the visitor runtime and the separate Agent runtime. Each uses its own short-lived token, state, and lifecycle.
Use the React Native SDK for native mobile CRM, support, marketplace, banking, hotel, travel, tourism, or customer apps. It includes a Managed UI preview for fast installs and a Headless object model for custom screens while platform-specific transport details stay internal.
Cross-platform visitor flow
One TypeScript API, native bridges where mobile needs them.
App requests token
Backend validates user
Ringnity returns SDK session
Ringnity Support
Online now
Overview
What this SDK is for
Use this React Native SDK for customer-facing support experiences or internal agent workspaces. Visitor and Agent runtimes remain separate.
Visitor / Client App
This page covers customer-facing React Native apps: chat, AI chat, internet voice call, video call, push notification, ringtone, Managed UI, and Headless UI.
Customer Backend Required
The React Native 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
The package reuses the canonical Agent runtime for inbox, presence, Team chat, calls, and push. Agent group media uses LiveKit after the required native setup.
Server and SDK topology
Architecture that must be set up
The SDK is embedded in the React Native app, but secret-bearing work belongs to the customer backend. This prevents developers from shipping the Ringnity Server API key inside the mobile bundle.
React Native 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 React Native
React Native 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, group membership, call-room control, and LiveKit media reuse the canonical Agent SDK without a duplicate React Native state layer.
Managed Support UI
Launch a complete support experience from React Native with Ringnity-provided screens.
Headless Chat
Create conversations and messages from your own native React Native screen.
AI Chat
Ask AI over HTTP and render the completed response through progressive callbacks.
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
Register mobile push tokens and play incoming call ringtone behavior.
Package shape
The React Native preview source package includes the TypeScript SDK surface and native module sources for the platform-specific mobile pieces.
@ringnity/react-native-sdk
TypeScript object model, generated operation layer, HTTP adapter,
Managed UI preview components, and Headless SDK objects.
native modules
Android/iOS call adapter sources, notification/ringtone bridge,
permissions helpers, and video view integration points.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 React Native.
Backend checklist
React Native compatibility
Server
Create the runtime token endpoint
Keep secrets on your backend while React Native 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: "react-native-app",
appVersion: req.body.appVersion,
platform: req.body.platform,
},
expiresIn: 900,
});
res.json({
token: context.contextToken,
expiresIn: context.expiresIn,
tokenType: context.tokenType,
});
});
app.listen(3000);React Native 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 React Native.
Server
Token lifecycle
Move from app entry to support session with a predictable flow. React Native 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 React Native build with a real customer.
React Native Setup
Download what you need
Current downloadable SDK bundle: 0.2.0-beta.0. Use the React Native SDK for the app and the Server API SDK for the customer backend token endpoint.
React Native SDK
TypeScript SDK, Managed UI preview, Headless object model, native call adapter sources, and basic example app.
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.
React Native Setup
Install the React Native SDK
During preview, download the source package and install it from a local package path. When public package publishing is enabled, this section can switch to npm coordinates.
1. Download and extract
Download the React Native SDK zip, extract it beside your app workspace, then install it as a local package.
Download React Native SDK# Pilot install from a local source package
npm install ./ringnity-react-native-sdk
# or
yarn add file:./ringnity-react-native-sdk
# iOS native module install
cd ios && pod install && cd ..React Native Setup
Initialize SDK
Create the SDK from a screen, hook, service, or app support module. The token provider calls the customer backend endpoint from the Server section.
import {
Ringnity,
createRingnityReactNativeCallAdapter,
} from '@ringnity/react-native-sdk';
const mediaAdapter = createRingnityReactNativeCallAdapter();
async function fetchRingnityRuntimeToken() {
const response = await fetch(
'https://your-backend.example.com/ringnity/runtime-token',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: currentUser.id,
name: currentUser.name,
email: currentUser.email,
appVersion: '1.0.0',
platform: Platform.OS,
}),
},
);
if (!response.ok) {
throw new Error('Ringnity runtime token request failed');
}
return response.json();
}
const ringnity = await Ringnity.create({
apiBaseUrl: 'https://api.ringnity.com',
tokenProvider: fetchRingnityRuntimeToken,
callMode: 'basic',
mediaAdapter,
notificationAdapter: mediaAdapter,
notifications: {
incomingCall: {
soundName: 'ringnity_default',
vibrate: true,
},
},
});Managed UI
Managed UI overview
Launch a complete Ringnity support screen with React Native components. Use Managed UI when the app wants Ringnity to draw the support screen.
import {
RingnityWidget,
createRingnityReactNativeCallAdapter,
} from '@ringnity/react-native-sdk';
const mediaAdapter = createRingnityReactNativeCallAdapter();
const config = {
tokenProvider: fetchRingnityRuntimeToken,
callMode: 'basic',
mediaAdapter,
notificationAdapter: mediaAdapter,
};
export function SupportScreen() {
return (
<RingnityWidget
config={config}
mode="full"
conversationSubject="Mobile customer support"
/>
);
}Custom video renderer
If the host app already owns video surfaces, inject remote and local preview views. Otherwise, use the default managed call surface.
<RingnityWidget
config={config}
mode="call"
remoteVideo={<YourRemoteVideoView />}
localPreview={<YourLocalPreviewView />}
/>
<RingnityVideoView
session={callSession}
remoteVideo={<YourRemoteVideoView />}
localPreview={<YourLocalPreviewView />}
/>Managed UI
Included features
With Managed UI, the feature implementation is bundled into RingnityWidget. The host app still owns backend token flow, native permissions, push 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/APNs; managed experiences benefit from the same device registration.
Ringtone
Managed foreground call experience can use the configured notification adapter.
Smaller managed surfaces
Use smaller managed surfaces when the app owns navigation but still wants Ringnity to render one support area, such as only Chat, AI, or Call.
<RingnityWidget config={config} mode="chat" />
<RingnityWidget config={config} mode="aiChat" />
<RingnityWidget config={config} mode="call" />
const ringnity = await Ringnity.create(config);
return (
<>
<RingnityChatWidget ringnity={ringnity} />
<RingnityCallWidget ringnity={ringnity} />
</>
);Headless UI
Headless UI overview
Use Ringnity as the support engine behind your own React Native 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
Each visitor feature can be used from custom React Native screens. The same secure tokenProvider and SDK instance power every feature.
The examples below are for Headless UI. Managed UI uses the same product capabilities through RingnityWidget.
Visitor Feature
Chat
Create a visitor conversation, send messages, and keep your screen updated while support is active.
Managed UI
Use RingnityWidget mode full or chat when Ringnity should render the conversation screen.
Headless UI
Use chat.createConversation, chat.sendMessage, and chat.subscribe when your app owns the entire chat UI.
const readiness = await ringnity.account.readiness();
const conversation = await ringnity.chat.createConversation({
subject: 'Need help with my order',
});
const conversationId = conversation.conversationId;
const message = await ringnity.chat.sendMessage({
conversationId,
body: 'Hi, I need help.',
});
await ringnity.chat.markRead({
conversationId,
messageId: message.messageId,
});
// Also available: availability, activeConversation, conversation,
// closeConversation, updateProfile, editMessage, deleteMessage, createAttachment,
// uploadAttachment, requestCall, and finalizeCallRequest. AI also exposes routing and searchKnowledge.
const updates = await ringnity.chat.subscribe(conversationId);
updates.on('conversation.updated', (event) => {
renderConversation(event.data);
});
updates.on('message.created', (event) => {
appendMessage(event.data);
});
// Call this when the screen unmounts or the conversation is closed.
updates.unsubscribe();Visitor Feature
AI Chat
Ask AI and render the answer in your own component, while plan readiness and tenant configuration stay controlled by Ringnity.
Managed UI
Use Managed UI when the AI surface should appear inside Ringnity support screens.
Headless UI
Use ai.streamChat for progressive rendering after the HTTP response completes; it is not transport streaming.
await ringnity.ai.streamChat(
{
conversationId,
message: 'Summarize the customer issue.',
},
{
onDelta: (event) => appendToChat(event.data.delta),
onDone: (event) => setAiAnswer(event.data.message),
onError: (event) => showAiError(event),
},
);Visitor Feature
Voice Call
Create voice call metadata and local controls. Production audio requires host-provided signalling, ICE/TURN, and remote media wiring.
Managed UI
Use Managed UI when Ringnity should render call controls.
Headless UI
Use calls.startAudio only with a host signalling transport; the native adapter controls local media.
const permissions = await mediaAdapter.permissions({ video: false });
if (permissions.canStartAudio) {
const call = await ringnity.calls.startAudio({
conversationId,
});
await call.mute();
await call.unmute();
await call.end();
}Visitor Feature
Video Call
Create video call metadata and local controls. Production video requires host-provided signalling, ICE/TURN, and remote media wiring.
Managed UI
Use Managed UI with default call surfaces or inject custom remote/local video views.
Headless UI
Use calls.startVideo only with host signalling and TURN; the native adapter controls local camera and views.
const permissions = await mediaAdapter.permissions({ video: true });
if (permissions.canStartVideo) {
const call = await ringnity.calls.startVideo({
conversationId,
});
await call.setVideoEnabled(false);
await call.setVideoEnabled(true);
await call.end();
}Visitor Feature
Push Notification
Register the device push token so Ringnity can target the visitor when background delivery is needed.
Managed UI
Managed UI benefits from the same registered device token but the host app still owns FCM/APNs setup.
Headless UI
Use devices.registerPushToken after your app obtains a device token from Firebase, APNs, or a push provider.
await ringnity.devices.registerPushToken({
platform: Platform.OS === 'ios' ? 'ios' : 'react_native',
provider: Platform.OS === 'ios' ? 'apns' : 'fcm',
token: pushToken,
audience: 'customer',
externalId: 'customer-123',
appId: 'com.example.customer',
});Visitor Feature
Ringtone
Play or preview incoming call sounds while the app is in the foreground, then stop when the call is answered, declined, or expired.
Managed UI
Managed foreground calls can use the configured notification adapter.
Headless UI
Use notifications.previewRingtone, playIncomingRingtone, and stopRingtone when your app owns the call invitation UI.
// Add Android asset: android/app/src/main/res/raw/ringnity_default.*
// Add iOS asset: ringnity_default.caf in the app bundle.
await ringnity.notifications.previewRingtone();
await ringnity.notifications.playIncomingRingtone();
// Stop when the customer answers, declines, or the call expires.
await ringnity.notifications.stopRingtone();Operations
Lifecycle
Keep setup predictable by treating the SDK as a runtime service owned by a support screen, app service, or provider.
App opens the support entry point.
React Native requests a runtime token from the customer backend.
Customer backend validates the app user and returns a short-lived context token.
React Native creates the Ringnity SDK instance with tokenProvider.
App 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 unsubscribes when leaving the screen.
App asks the backend for a new token when the SDK session expires.
Operations
Troubleshooting
Most integration failures come from missing tenant context, token exchange mistakes, or mobile permission setup.
MISSING_TENANT
Check that the backend exchanges context with the correct tenant slug before returning the SDK session token.
SDK_ORIGIN_NOT_ALLOWED
For mobile SDK sessions, check allowed app/domain configuration and make sure the token was created for the right tenant.
401 or expired token
Request a new runtime token from the customer backend and recreate or refresh the SDK instance.
No call audio/video
Check microphone/camera permissions, native module linking, and whether the host app is using Expo managed mode without a custom dev client.
No push notification
Confirm FCM/APNs token registration, appId, audience customer, and externalId.
Operations
Full example
Use Managed UI for the fastest install. Use Headless UI when the product screen must follow the host app design system.
import { useEffect, useRef, useState } from 'react';
import { Platform } from 'react-native';
import {
Ringnity,
RingnityWidget,
createRingnityReactNativeCallAdapter,
} from '@ringnity/react-native-sdk';
const mediaAdapter = createRingnityReactNativeCallAdapter();
async function fetchRingnityRuntimeToken() {
const response = await fetch(
'https://your-backend.example.com/ringnity/runtime-token',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: currentUser.id,
name: currentUser.name,
email: currentUser.email,
platform: Platform.OS,
}),
},
);
return response.json();
}
const config = {
tokenProvider: fetchRingnityRuntimeToken,
callMode: 'basic',
mediaAdapter,
notificationAdapter: mediaAdapter,
};
export function SupportScreen() {
return (
<RingnityWidget
config={config}
mode="full"
conversationSubject="Mobile customer support"
/>
);
}
export function HeadlessSupportScreen() {
const [messages, setMessages] = useState([]);
const subscriptionRef = useRef(null);
useEffect(() => {
let disposed = false;
async function start() {
const ringnity = await Ringnity.create(config);
const conversation = await ringnity.chat.createConversation({
subject: 'Mobile customer support',
});
if (disposed) return;
subscriptionRef.current = await ringnity.chat.subscribe(
conversation.conversationId,
);
subscriptionRef.current.on('message.created', (event) => {
setMessages((current) => [...current, event.data]);
});
await ringnity.chat.sendMessage({
conversationId: conversation.conversationId,
body: 'Hi, I need help.',
});
}
start();
return () => {
disposed = true;
subscriptionRef.current?.unsubscribe();
};
}, []);
return <YourCustomSupportView messages={messages} />;
}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>.
