Beginner friendly

Backend Token Server

Use this guide when a browser or mobile app needs a short-lived Ringnity runtime token. Most customers do not need a new server. They add one small endpoint to the backend they already own.

What you build

Android / iOS / Web app
  -> customer backend
  -> Ringnity Server API
  <- short-lived runtime token
  <- app uses the token

What stays secret

`RINGNITY_SERVER_API_KEY` stays on the customer backend. Do not put it in Android, iOS, Flutter, React Native, browser bundles, public repositories, or app assets.

RINGNITY_API_BASE_URL=https://api.ringnity.com
RINGNITY_SERVER_API_KEY=rn_sk_live_xxx

Step-by-step

  1. 1Create or renew a Server API key from SDK Variables.
  2. 2Store the full key in the customer backend secret manager.
  3. 3Create POST /ringnity/runtime-token in the customer backend.
  4. 4The endpoint verifies the app user with the customer auth system.
  5. 5The endpoint calls Ringnity Server API with the secret key.
  6. 6The app receives only a short-lived runtime token.

Node.js / Express

This is the easiest path when the customer backend uses Node.js. The official Server API helper hides the Ringnity HTTP details.

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 login/session check.
  // Return the customer currently using your app.
  return {
    id: req.body.userId || "customer-123",
    name: req.body.name || "Demo Customer",
    email: req.body.email || "customer@example.com"
  };
}

app.post("/ringnity/runtime-token", async (req, res) => {
  const customer = await requireCustomer(req);

  const result = await ringnity.tokens.visitorContext({
    visitor: {
      externalId: customer.id,
      name: customer.name,
      email: customer.email
    },
    metadata: {
      source: "android-app"
    },
    expiresIn: 900
  });

  // Keep the mobile response simple and consistent.
  res.json({
    token: result.contextToken,
    expiresIn: result.expiresIn,
    tokenType: result.tokenType
  });
});

app.listen(3000, () => {
  console.log("Token server listening on http://localhost:3000");
});

Go backend

Go customers can call Ringnity Server API with normal HTTP. No Go SDK is required for the token exchange.

package main

import (
  "bytes"
  "encoding/json"
  "log"
  "net/http"
  "os"
)

func env(name string, fallback string) string {
  value := os.Getenv(name)
  if value == "" {
    return fallback
  }
  return value
}

func runtimeToken(w http.ResponseWriter, r *http.Request) {
  payload := map[string]any{
    "visitor": map[string]any{
      "externalId": "customer-123",
      "name": "Demo Customer",
      "email": "customer@example.com",
    },
    "metadata": map[string]any{
      "source": "android-app",
    },
    "expiresIn": 900,
  }

  body, _ := json.Marshal(payload)
  req, _ := http.NewRequest(
    "POST",
    env("RINGNITY_API_BASE_URL", "https://api.ringnity.com")+"/api/server/visitor-context-token",
    bytes.NewReader(body),
  )
  req.Header.Set("Authorization", "Bearer "+os.Getenv("RINGNITY_SERVER_API_KEY"))
  req.Header.Set("Content-Type", "application/json")

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    http.Error(w, "Ringnity request failed", http.StatusBadGateway)
    return
  }
  defer resp.Body.Close()

  var ringnity struct {
    Success bool `json:"success"`
    Data struct {
      ContextToken string `json:"contextToken"`
      ExpiresIn int `json:"expiresIn"`
      TokenType string `json:"tokenType"`
    } `json:"data"`
  }
  json.NewDecoder(resp.Body).Decode(&ringnity)

  if resp.StatusCode >= 400 || !ringnity.Success {
    http.Error(w, "Ringnity token exchange failed", http.StatusBadGateway)
    return
  }

  json.NewEncoder(w).Encode(map[string]any{
    "token": ringnity.Data.ContextToken,
    "expiresIn": ringnity.Data.ExpiresIn,
    "tokenType": ringnity.Data.TokenType,
  })
}

func main() {
  http.HandleFunc("/ringnity/runtime-token", runtimeToken)
  log.Fatal(http.ListenAndServe(":3000", nil))
}

Java Spring Boot

Spring Boot customers can keep the key in application secrets and use `RestClient` to request runtime tokens.

package com.example.demo;

import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;

record RuntimeTokenResponse(String token, Integer expiresIn, String tokenType) {}

@RestController
public class RingnityTokenController {
  private final RestClient restClient;

  @Value("${ringnity.server-api-key}")
  private String serverApiKey;

  public RingnityTokenController(
      @Value("${ringnity.api-base-url:https://api.ringnity.com}") String apiBaseUrl
  ) {
    this.restClient = RestClient.builder().baseUrl(apiBaseUrl).build();
  }

  @PostMapping("/ringnity/runtime-token")
  public RuntimeTokenResponse runtimeToken() {
    // Replace this with your own Spring Security principal/session lookup.
    Map<String, Object> body = Map.of(
      "visitor", Map.of(
        "externalId", "customer-123",
        "name", "Demo Customer",
        "email", "customer@example.com"
      ),
      "metadata", Map.of("source", "android-app"),
      "expiresIn", 900
    );

    Map<?, ?> response = restClient.post()
      .uri("/api/server/visitor-context-token")
      .header(HttpHeaders.AUTHORIZATION, "Bearer " + serverApiKey)
      .body(body)
      .retrieve()
      .body(Map.class);

    Map<?, ?> data = (Map<?, ?>) response.get("data");

    return new RuntimeTokenResponse(
      (String) data.get("contextToken"),
      (Integer) data.get("expiresIn"),
      (String) data.getOrDefault("tokenType", "Bearer")
    );
  }
}

Test the endpoint

curl -X POST "http://localhost:3000/ringnity/runtime-token" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "customer-123",
    "name": "Demo Customer",
    "email": "customer@example.com"
  }'

Expected response

{
  "token": "eyJhbGciOi...",
  "expiresIn": 900,
  "tokenType": "Bearer"
}

Use it from Android

Android calls the customer backend endpoint and passes the returned token into the Kotlin SDK. The Android app never sees the Server API key.

suspend fun fetchRingnityToken(): RingnityRuntimeToken {
    val response = customerBackend.post("/ringnity/runtime-token")

    return RingnityRuntimeToken(
        token = response.token,
        expiresIn = response.expiresIn,
        tokenType = response.tokenType ?: "Bearer"
    )
}

val ringnity = Ringnity.create(
    RingnityConfig(
        tokenProvider = ::fetchRingnityToken,
        callMode = RingnityCallMode.BASIC
    )
)

Production checklist

  • Verify the customer's own user session before issuing a token.
  • Use HTTPS only.
  • Keep token expiry short, usually 900 seconds.
  • Log failures without printing `RINGNITY_SERVER_API_KEY` or runtime tokens.
  • Rotate the Server API key if it was copied into the wrong place.

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