开发者参考

在你的后端签发令牌

签发入房令牌的受支持方式——以及那个一旦缺失、就会让单会话强制静默失效的字段。

返回开发者产品

为什么放在后端?

服务器密钥绝不随应用分发。任何打包进 APK 的密钥都可被提取,并用来冒充任意用户。

把设备信息透传过来

你的服务器看不到设备。应用把这些值发给你的服务器,服务器在同一个请求里转交给我们。

失败是静默的

没有 device_id,"一个账号一台设备"的检查会提前返回,既不报错也不记录日志。令牌照常签发,保护却是关闭的。

实测,而非推测

在三小时的真实入房中:后端签发的令牌共 3,224 次入房——100% 没有设备标识;套件签发的令牌 1,641 次——0%。

令牌请求字段

在请求头带上服务器密钥,POST 到 /api/v1/token。

字段级别含义
user_idstring必填Who the token is for. Your own user id. (Previously `identity` — still accepted.)
room_idstring必填Which room they are joining. (Previously `room_name` — still accepted.)
device_idstring建议A 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_modelstring建议e.g. SM-A175F. Feeds per-handset quality analysis — which models have audio or video trouble.
osstring建议android | ios.
os_versionstring建议e.g. 14.
app_versionstring建议Your app's version, so a regression can be traced to a release.
display_namestring可选Shown to other participants. Omit it and the name stays empty — we never substitute the user id for it.
rolestring可选Only 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.
typestring可选audio_room | live_stream. Legacy kits send `service` (+ `kind`) instead and the type is derived.

在你的服务器上

密钥只存在于此。标注的字段来自应用——服务器自己无从得知。

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,
  }),
});

在你的应用里

把设备信息发给自己的后端,由它转交。设备标识必须在应用重启后保持不变。

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,
});

拒绝码(403)

每次拒绝都带 code。请读取该码——只有第一种才应显示"你已被移出"。

代码含义如何处理
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.

认证模式

引擎会在每次入房时记录认证模式,便于你核实自己真正走的是哪条路径。

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.

准备好与 UTD 一起构建了吗?

创建账户,为主钱包充值,按需开启你所需要的服务。