Docs

Developer documentation

Add voice, video and live rooms to your app. Your server signs a short-lived token, your app joins with it.

Quick start

Three steps from an empty project to people talking in a room.

  1. Get your App ID and App Certificate

    Sign up free (10,000 free minutes) or log in to your dashboard. Each app has an App ID, an App Certificate and a region URL. The App Certificate is a secret: it goes on your server and nowhere else.

  2. Add a token endpoint to your server

    Your backend signs a short-lived access token for a signed-in user and one room, with our token library for Node or Go. No Gravix service is called to make a token.

  3. Join from your app

    Install the SDK for your platform: flutter pub add gravix_rtc, or npm install gravix-rtc for the web. It asks your endpoint for a token, then connects to the room with it. Mic, camera and remote video are a few lines from there.

Access tokens

An access token lets one user into one room for a limited time. It is a signed JWT that carries the room, the user's identity and what they may do (for example publish audio and video, or only listen). Gravix checks the signature when the user joins.

Tokens are signed with your App Certificate. Whoever holds it can let anyone into any of your rooms, and that usage is billed to you. So:

  • Keep the App Certificate on your server, in an environment variable or a secrets store. Never put it in app code, a mobile build or a web bundle.
  • Sign a token only for a user your backend has already authenticated, and take their identity from your own session.
  • Decide on the server whether a user may publish or only listen.
  • Keep tokens short-lived. They last 6 hours unless you set another time, and 24 hours at most.

Your token endpoint

Install gravix-rtc-token from npm (Node 18+, Deno, Bun and edge runtimes) or use gravix-token-go (Go 1.21+). Both take the same options. The endpoint below is all a backend needs for joining.

server.ts
// server.ts (your backend, Node 18+). This file never ships inside an app.
import express from 'express';
import { createToken } from 'gravix-rtc-token';

const APP_ID = process.env.GRAVIX_APP_ID!;                   // from your dashboard
const APP_CERTIFICATE = process.env.GRAVIX_APP_CERTIFICATE!; // server only, never in an app
const REGION_URL = process.env.GRAVIX_URL!;                   // wss://YOUR-REGION-URL

const app = express();
app.use(express.json());

// requireUser is your own login check: the identity comes from YOUR session,
// never from the request body.
app.post('/rtc/token', requireUser, async (req, res) => {
  const room = req.body?.room;
  if (typeof room !== 'string' || !/^[\w.-]{1,64}$/.test(room)) {
    return res.status(400).json({ error: 'invalid room' });
  }
  const token = await createToken(APP_ID, APP_CERTIFICATE, {
    room,                       // the library adds your App ID prefix
    identity: req.user.id,
    name: req.user.name,
    canPublish: true,           // false = listen only; decide it here, not in the app
    ttlSeconds: 60 * 60,        // short-lived: 1 hour
  });
  res.json({ token, url: REGION_URL });
});

app.listen(3000);
token.go
// token.go (your backend, Go 1.21+). gravixtoken is the gravix-token-go package.
func tokenHandler(w http.ResponseWriter, r *http.Request) {
	user, ok := currentUser(r) // your own login check
	if !ok {
		http.Error(w, "sign in first", http.StatusUnauthorized)
		return
	}
	var body struct {
		Room string `json:"room"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Room == "" {
		http.Error(w, "invalid room", http.StatusBadRequest)
		return
	}

	// The App Certificate stays in the server's environment.
	jwt, err := gravixtoken.New(os.Getenv("GRAVIX_APP_ID"), os.Getenv("GRAVIX_APP_CERTIFICATE")).
		Room(body.Room).
		Identity(user.ID).
		Name(user.Name).
		CanPublish(true). // false = listen only
		TTL(time.Hour).   // short-lived
		ToJWT()
	if err != nil {
		http.Error(w, "could not sign", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"token": jwt,
		"url":   os.Getenv("GRAVIX_URL"), // wss://YOUR-REGION-URL
	})
}

The SDKs post { room, identity, name, can_publish } to your endpoint and expect { token, url } back, where url is your region URL from the dashboard. Treat the request body as a wish, not a fact: the identity and the publish right are yours to set.

Join from the app

The app never sees your App Certificate. It points the SDK at your endpoint and joins.

lib/call.dart
import 'package:gravix_rtc/gravix_rtc.dart';

// One provider per signed-in user. It caches each token until it is about to expire.
final tokens = GravixTokenProvider.endpoint(
  Uri.parse('https://YOUR-BACKEND/rtc/token'),
  headers: {'Authorization': 'Bearer $sessionJwt'}, // your app's own login session
);

final room = GravixRoomService();

Future<void> join(String roomId, String userId, String name) async {
  room.onRemoteVideoTrack = (uid, track) {
    // show it with VideoTrackRenderer(track)
  };

  final ok = await room.connectWithTokenProvider(
    tokenProvider: tokens,
    request: GravixTokenRequest(
      room: roomId,
      identity: userId,
      name: name,
      canPublish: true,
    ),
    publishMic: true,
    enableVideo: true,
  );
  if (!ok) {
    // room.lastTokenError is set when your token endpoint failed
  }
}

Future<void> leave() => room.disconnect();
call.ts
import { Room, RoomEvent, GravixTokenProvider, connectWithTokenProvider } from 'gravix-rtc';

// Calls YOUR backend with the user's session cookie. Tokens are cached until they expire.
const tokens = GravixTokenProvider.endpoint('https://YOUR-BACKEND/rtc/token', {
  credentials: 'include',
});

const room = new Room();
const stage = document.getElementById('stage')!;

room
  .on(RoomEvent.TrackSubscribed, (track) => {
    stage.appendChild(track.attach()); // a <video> or <audio> element
  })
  .on(RoomEvent.TrackUnsubscribed, (track) => {
    track.detach().forEach((el) => el.remove());
  });

export async function join(roomId: string, userId: string, name: string) {
  await connectWithTokenProvider(room, {
    tokenProvider: tokens,
    request: { room: roomId, identity: userId, name },
  });
  await room.localParticipant.setMicrophoneEnabled(true);
  await room.localParticipant.setCameraEnabled(true);
}

export const leave = () => room.disconnect();

Android and iOS apps use the Flutter SDK today. Platform setup (permissions, background audio) is on the SDKs page.

SDKs

One room model on every platform. Pick yours for install steps, platform setup and a complete join example.

Guides

Beauty filter and Studio

The real-time beauty filter is included free with every subscription, and Gravix Studio lets you design camera looks in the browser. Read what each one does before you wire it into your app.

Get help

Stuck on a token, a permission or a platform build? Talk to a person.

Get your keys

Sign up free to get your App ID, App Certificate and 10,000 free minutes, or log in to your dashboard.