Developer reference

Minting tokens from your backend

The supported way to issue a join token — and the one field that, if dropped, silently disables single-session enforcement.

Back to developer products

Why from the backend?

Your server secret never ships in the app. Any key bundled into an APK can be extracted and used to impersonate any user.

Forward the device facts

Your server cannot see the device. The app sends these to your server, and your server passes them through in the same request.

The failure is silent

Without device_id, one-account-one-device returns early with no error and no log. The token succeeds and the protection is simply off.

Measured, not assumed

Over three hours of real joins: backend-minted tokens produced 3,224 joins — 100% with no device id. Kit-minted tokens produced 1,641 joins — 0%. The same project shows both results depending on who minted.

Token request fields

POST to /api/v1/token with your server secret in the header.

FieldLevelMeaning
user_idstringRequiredWho the token is for. Your own user id. (Previously `identity` — still accepted.)
room_idstringRequiredWhich room they are joining. (Previously `room_name` — still accepted.)
device_idstringRecommendedA stable id for the PHYSICAL DEVICE — not the user, and not the session. This is what enforces one account on one device: when the same user_id joins from a different device_id, the previous device is messaged and removed. Omit it and that enforcement silently does nothing: the check returns early, logs nothing, and the old device stays signed in.
device_modelstringRecommendede.g. SM-A175F. Feeds per-handset quality analysis — which models have audio or video trouble.
osstringRecommendedandroid | ios.
os_versionstringRecommendede.g. 14.
app_versionstringRecommendedYour app's version, so a regression can be traced to a release.
display_namestringOptionalShown to other participants. Omit it and the name stays empty — we never substitute the user id for it.
rolestringOptionalOnly honoured from a server-signed request, and only while your project still carries the client-asserted-role exception. The supported path is PUT /rooms/:room/participants/:id/role.
typestringOptionalaudio_room | live_stream. Legacy kits send `service` (+ `kind`) instead and the type is derived.

On your server

The secret lives here and nowhere else. The highlighted fields come from the app — your server cannot know them on its own.

mint-token.js
// Your backend — the app never sees the server secret.
const res = await fetch("https://engine.udt-stream.com/api/v1/token", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-App-Secret": process.env.UTD_SERVER_SECRET,   // never ship this in the app
  },
  body: JSON.stringify({
    user_id: currentUser.id,
    room_id: roomId,

    // 🔴 Forwarded FROM THE APP. Your server cannot know these on its own,
    // and without device_id one-account-one-device stops working for your users.
    device_id: body.device_id,
    device_model: body.device_model,
    os: body.os,
    os_version: body.os_version,
    app_version: body.app_version,
  }),
});

In your app

Send the device facts to your own backend, which forwards them. The device id must stay stable across app restarts.

request_token.dart
// Your app — send the device facts to YOUR backend, which forwards them to us.
final deviceId = await MyDeviceIdentity.stableId(); // persisted, survives app restarts
await myApi.post("/rooms/$roomId/token", body: {
  "device_id": deviceId,
  "device_model": deviceInfo.model,
  "os": Platform.isAndroid ? "android" : "ios",
  "os_version": deviceInfo.version,
  "app_version": packageInfo.version,
});

Refusal codes (403)

Every refusal carries a code. Read the code — only the first one should ever produce a "you were removed" message.

CodeMeaningWhat to do
user_bannedThis user is banned from this room.Show them they were removed. This is the ONLY code that should produce that message.
room_type_disabledThe project does not have this room type enabled.A configuration problem, not a user problem. Never show a removal notice.
streaming_disabledThe streaming service is not enabled for this project.Same — configuration, not the user.
appkey_identity_mint_disabledYou tried to mint an identity-bearing token with the publishable app_key.Mint from your backend with the server secret instead. This is the path this page describes.

Auth modes

The engine records the auth mode on every join, so you can verify which path you are actually on.

secret / signature / bearer

Your backend, authenticated with your server secret. The recommended path.

app_key

The device, using the publishable app key. The legacy path — being closed.

Ready to build with UTD?

Create your account, fund your master wallet, and turn on the services you need.