Build the backend token endpoint for Ringnity SDK apps
Use this guide when Android, iOS, React Native, Flutter, Web, or another customer app needs a Ringnity runtime token. The app asks the customer backend for a short-lived token. The customer backend keeps the Ringnity Server API key private.
Visitor App
Android, Swift, React Native, Flutter, or Web.
Customer Backend
Authenticates user and stores Server API key.
Ringnity API
Creates short-lived SDK runtime token.
Architecture
The backend is the security gate
Do not let mobile or browser apps create Ringnity tokens directly with the Server API key. Your backend validates the customer app user, creates trusted visitor context, and returns only the short-lived token needed by the SDK.
Visitor App -> POST /ringnity/runtime-token -> Customer Backend -> POST /api/server/visitor-context-token -> Ringnity API <- short-lived token <- SDK initializes with tokenProvider
What the app receives
The app receives the smallest possible response: token, expiry, and token type. It does not receive the Server API key, tenant internal id, credential metadata, or scopes.
{
"token": "eyJhbGciOi...",
"expiresIn": 900,
"tokenType": "Bearer"
}Boundaries
Separate app, customer backend, and Ringnity responsibilities
This split keeps SDK integration easy for mobile teams while preserving the security model that prevents secret keys from entering client apps.
Customer backend
Owns user authentication, checks who is allowed to request support, stores the Server API key, and issues short-lived Ringnity runtime tokens.
Ringnity Server API
Validates the Server API key, tenant, scope, and trusted visitor context, then returns a short-lived token for the SDK runtime.
Mobile or web app
Calls the customer backend token endpoint, receives only a short-lived token, and initializes the visitor SDK with tokenProvider.
Server setup
Backend requirements
The sample below uses Node.js and Express, but the same contract works from Java, Go, PHP, Laravel, Rails, .NET, or any backend that can send HTTPS requests.
Node.js 18+ for the sample server. Any backend language can use the same HTTP contract.
HTTPS in staging and production. Local HTTP is acceptable only for development.
Server API key with server:visitor-token:write. Add agent/admin token scopes only when needed.
Environment variable, vault, secret manager, or platform secret. Never mobile source code.
Validate the app user before creating a Ringnity token.
Short expiry, usually 900 seconds for visitor sessions.
Scopes
Use the final Server API scope names
For visitor app samples, the customer backend needs server:visitor-token:write. Add the other token scopes only if the same backend also issues Agent or Admin SDK tokens.
Required for Visitor/Client App SDK token exchange.
Only needed if this backend also issues Agent SDK tokens.
Only needed if this backend also issues Admin SDK tokens.
Optional readiness and tenant checks.
Optional health check for credential validation.
Secrets
Store the Ringnity Server API key on the backend only
Create the Server API key from SDK Variables or Dashboard Developer settings, then put the secret into your backend environment or secret manager.
RINGNITY_API_BASE_URL=https://api.ringnity.com RINGNITY_SERVER_API_KEY=rn_sk_live_xxx
Do not copy the key into app code
Android APK, iOS IPA, React Native bundles, Flutter binaries, browser JavaScript, and app remote config are not safe places for Server API keys.
Node.js
Create POST /ringnity/runtime-token
This endpoint belongs to the customer backend. It validates the customer app user, asks Ringnity for a trusted visitor context token, then returns the runtime token to the app.
npm install @ringnity/server-api express cors helmet
import cors from "cors";
import express from "express";
import helmet from "helmet";
import { RingnityServerApi } from "@ringnity/server-api";
const app = express();
app.use(helmet());
app.use(cors({ origin: "https://app.customer.com", credentials: true }));
app.use(express.json());
const ringnity = RingnityServerApi.create({
apiBaseUrl: process.env.RINGNITY_API_BASE_URL || "https://api.ringnity.com",
apiKey: process.env.RINGNITY_SERVER_API_KEY
});
async function requireCustomer(req) {
// Replace this with your real customer auth/session lookup.
// Return null or throw when the app user is not allowed to request support.
return {
id: req.body.userId || "customer-123",
name: req.body.name || "Demo Customer",
email: req.body.email || "customer@example.com",
phone: req.body.phone || null
};
}
app.post("/ringnity/runtime-token", async (req, res) => {
try {
const customer = await requireCustomer(req);
const result = await ringnity.tokens.visitorContext({
visitor: {
externalId: customer.id,
name: customer.name,
email: customer.email,
phone: customer.phone
},
metadata: {
source: req.body.source || "visitor-app",
appUserId: customer.id
},
expiresIn: 900
});
res.json({
token: result.contextToken,
expiresIn: result.expiresIn,
tokenType: result.tokenType || "Bearer"
});
} catch (error) {
console.error("[ringnity] runtime token failed", {
message: error?.message,
code: error?.code,
status: error?.status
});
res.status(502).json({
code: "RINGNITY_TOKEN_FAILED",
message: "Unable to create Ringnity runtime token."
});
}
});
app.listen(3000, () => {
console.log("Ringnity token server listening on http://localhost:3000");
});HTTP contract
Call Ringnity directly if you are not using the Node helper
The helper package is optional. Backends in other languages can call the Server API endpoint with normal HTTPS and the same server:visitor-token:write scope.
curl -X POST "https://api.ringnity.com/api/server/visitor-context-token" \
-H "Authorization: Bearer $RINGNITY_SERVER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"visitor": {
"externalId": "customer-123",
"name": "Demo Customer",
"email": "customer@example.com"
},
"metadata": {
"source": "android-app"
},
"expiresIn": 900
}'Test your own customer backend endpoint separately. The app should call your backend, not the Ringnity Server API.
curl -X POST "https://customer-api.example.com/ringnity/runtime-token" \
-H "Authorization: Bearer CUSTOMER_APP_SESSION" \
-H "Content-Type: application/json" \
-d '{
"source": "android-app"
}'App contract
Return one simple response shape to every client app
Use the same response body for Android, Swift, React Native, Flutter, and Web. That makes each SDK sample easier to maintain.
{
"token": "eyJhbGciOi...",
"expiresIn": 900,
"tokenType": "Bearer"
}SDK wiring
Each SDK calls the customer backend token endpoint
The app-specific SDK code is small. Each platform provides a tokenProvider function that calls the customer backend and returns the token response.
Android Kotlin tokenProvider
suspend fun fetchRingnityToken(): RingnityRuntimeToken {
val response = customerBackend.post<RuntimeTokenResponse>(
"/ringnity/runtime-token",
mapOf("source" to "android-app")
)
return RingnityRuntimeToken(
token = response.token,
expiresIn = response.expiresIn,
tokenType = response.tokenType ?: "Bearer"
)
}
val ringnity = Ringnity.create(
RingnityConfig(
tokenProvider = ::fetchRingnityToken,
callMode = RingnityCallMode.BASIC
)
)Swift tokenProvider
func fetchRingnityToken() async throws -> RingnityRuntimeToken {
let request = RuntimeTokenRequest(source: "ios-app")
let response: RuntimeTokenResponse = try await customerBackend.post(
"/ringnity/runtime-token",
body: request
)
return RingnityRuntimeToken(
token: response.token,
expiresIn: response.expiresIn,
tokenType: response.tokenType ?? "Bearer"
)
}
let ringnity = RingnityClient(
configuration: .init(tokenProvider: fetchRingnityToken)
)React Native tokenProvider
import { createRingnity } from "@ringnity/react-native-sdk";
async function fetchRingnityToken() {
const response = await fetch("https://customer-api.example.com/ringnity/runtime-token", {
method: "POST",
headers: {
"Authorization": "Bearer CUSTOMER_APP_SESSION",
"Content-Type": "application/json"
},
body: JSON.stringify({ source: "react-native-app" })
});
if (!response.ok) throw new Error("Ringnity token request failed");
return response.json();
}
export const ringnity = createRingnity({
tokenProvider: fetchRingnityToken
});Flutter tokenProvider
Future<RingnityRuntimeToken> fetchRingnityToken() async {
final response = await customerBackend.post(
'/ringnity/runtime-token',
data: {'source': 'flutter-app'},
);
return RingnityRuntimeToken(
token: response.data['token'] as String,
expiresIn: response.data['expiresIn'] as int,
tokenType: response.data['tokenType'] as String? ?? 'Bearer',
);
}
final ringnity = Ringnity.create(
RingnityConfig(tokenProvider: fetchRingnityToken),
);Lifecycle
Issue short-lived tokens and refresh them through tokenProvider
The SDK can ask tokenProvider for a fresh token when the app starts, before support begins, or after an unauthorized runtime response.
- 1App opens the support entry point.
- 2App calls POST /ringnity/runtime-token on the customer backend.
- 3Customer backend validates the app user/session.
- 4Customer backend calls Ringnity Server API with the Server API key.
- 5Ringnity returns a short-lived visitor context token.
- 6Customer backend returns only token, expiresIn, and tokenType to the app.
- 7SDK initializes or refreshes with tokenProvider.
Security
Production checklist
This checklist is the minimum before sharing a sample app with external developers or a real customer.
- Never ship RINGNITY_SERVER_API_KEY in Android, iOS, Flutter, React Native, browser code, remote config, public repos, logs, crash reports, or app assets.
- Validate the customer app user before issuing the token.
- Keep runtime token responses small. Do not return credential id, tenant internal id, scopes, or debug payloads to the app.
- Use HTTPS and normal customer auth protections such as session cookies, bearer tokens, or signed app requests.
- Rate-limit the token endpoint by user, device, IP, and tenant when exposed publicly.
- Log token issuance metadata without printing tokens or secret keys.
Troubleshooting
Common errors
Most token server issues come from missing Server API keys, missing final scopes, or app code calling runtime APIs before tokenProvider resolves.
The backend did not send Authorization: Bearer SERVER_API_KEY.
The Server API key does not include server:visitor-token:write. Create or update the credential from SDK Variables.
For web, add the real website domain. For mobile, make sure the customer backend is not forwarding an invalid Origin header.
The app tried to call SDK runtime APIs before tokenProvider returned a valid token.
The request did not resolve to a tenant. Check the Server API key, tenant slug, and backend environment.
Downloads
Download the backend helper and the SDK platform package
Current preview bundle: 0.2.0-beta.0. Every mobile sample should use the matching platform SDK plus the Server API SDK for the customer backend token endpoint.
Server API SDK
Node backend helper for visitor, agent, and admin token exchange. This is the package used by the token server.
OpenAPI Contract
Exact REST schema for Server API and SDK runtime calls.
Android Kotlin SDK
Use with this backend endpoint when building Android visitor apps.
Swift SDK
Use with this backend endpoint when building iOS visitor apps.
React Native SDK
Use with this backend endpoint when building React Native visitor apps.
Flutter SDK
Use with this backend endpoint when building Flutter visitor apps.
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>.
