Build · Client integration

Authorization and revocation

Give each client the operations it needs. Apply visibility separately, and understand where revocation takes effect.

A product user authenticates with your application. An RMC participant authenticates with a room token. Your backend connects the two: create or locate a room, decide what that user may do, and return a participant credential with those grants.

For the identity model behind these checks, start with participants. The architecture walkthrough shows where current authority is applied.

Your backend authenticates a product user and issues a scoped room token. Delivery requires a current action grant, matching event visibility, and a matching subscription selector. Revocation ends further access but cannot recall already delivered data.
Current grants, visibility, and selectors answer three different questions.Open SVG ↗Excalidraw source ↓

Bootstrap on your backend

TypeScript
const created = await client.createRoom("support", serverAdminToken);
const owner = client.room(created.room.id, created.token);
await owner.createChannel({id: "chat", type: "text", direction: "bidirectional"});

After authenticating the application user, issue the required grants:

TypeScript
const browser = await owner.invite("customer", [
  {action: "event:publish", resource: "chat"},
  {action: "event:subscribe", resource: "chat"},
]);

const session = {
  roomId: created.room.id,
  participantId: browser.participant.id,
  token: browser.token,
};

Return only session through the authenticated application endpoint. Here, client is an RmcClient and serverAdminToken is a backend secret. The invitation API accepts the grants provided by an authorized inviter; it is not a general user-policy engine. Treat participant:invite as a privileged operation. The product decides which grants to issue.

Keep the admin token and owner credential on the server. RMC room tokens are bearer credentials, so possession is authority. Do not put them in URLs, event payloads, example logs, or public build-time configuration. The quickstart demonstrates trusted bootstrap and separate participant credentials.

Grants answer “may this participant do this?”

A grant matches an action and a resource. Both accept exact strings, *, or a trailing prefix wildcard. A role name is not an implicit collection of grants.

Operation Action Resource
Publish a channel event event:publish Channel ID, such as chat
Read a channel event in history or subscription event:subscribe Channel ID
Publish/read a channel-less event event:publish / event:subscribe Event type, such as state.patched
Include a channel definition in a snapshot channel:read or event:subscribe Channel ID
Read / write a state document state:read / state:write Document key
Read / write an artifact artifact:read / artifact:write Artifact type, not artifact ID
Read / start a delegation delegation:read / delegation:start Task key
Cancel a delegation delegation:cancel Delegation ID
Read conversation turns conversation:read Turn ID
Invite / revoke a participant participant:invite / participant:revoke * for invite; target participant ID for revoke
Close the room room:close Room ID

Read permission for a snapshot projection does not automatically grant access to its corresponding events. For example, a state view that hydrates preferences and consumes state.patched needs both state:read on preferences and event:subscribe on state.patched, subject to visibility. Event authorization for that channel-less type is type-wide, not a per-document-key selector.

The exact checks live in auth.go, session.go, and the HTTP handlers. Media has additional scoped grant rules.

Visibility answers “who is this fact for?”

An event must be visible and allowed by the participant's subscribe grant. A selector is an additional filter after these checks. It cannot reveal otherwise inaccessible records.

TypeScript
await room.channel("chat").publish("support.note.created", {
  text: "A note for the assigned specialist.",
}, {
  visibility: {participant_ids: [specialistParticipantId]},
});

specialistParticipantId must be an RMC participant ID for this room. The specialist still needs event:subscribe on chat.

Visibility value Meaning
Omitted / empty Room-visible, subject to action grants
{audience: "room"} Room-visible; this branch takes precedence over recipient lists
{participant_ids: [id]} Visible to a listed participant
{roles: ["specialist"]} Visible to participants with a listed role
Participant IDs and roles together Either list may match
{audience: "internal"} Requires internal:read on *, as well as the operation's grant

For restricted recipients, omit audience: "room". Adding recipient lists to a room-visible event does not narrow that visibility. This behavior is explicit in VisibleTo.

Revocation is enforced during use

The participant session loads current authority for publication, history, snapshots, and subscriptions. A publication queued before revocation rechecks authority when it reaches the room command. An existing subscription cannot keep using the grants from its initial handshake indefinitely; revocation also wakes idle readers.

TypeScript
// Trusted owner/backend: the SDK has no revoke helper at this revision.
const response = await fetch(
  `${baseUrl}/v1/rooms/${encodeURIComponent(roomId)}/participants/${encodeURIComponent(participantId)}`,
  {method: "DELETE", headers: {Authorization: `Bearer ${ownerToken}`}},
);
if (!response.ok) throw new Error(`Revoke failed: HTTP ${response.status}`);

Revocation prevents further authorized use; it cannot recall a payload already delivered to a browser or cancel arbitrary application code already running in a callback. Media cleanup is a separate provider operation. RMC commits local revocation and durable cleanup intent before trying provider removal, and retries failures. A 502 can therefore mean revoked locally, provider cleanup still pending. See the cleanup boundary.

Read the revocation subscription tests, multiplex tests, and media lifecycle tests for the enforced cases.

Keep privileged APIs at the host boundary

Embedded Go code can open an authenticated RoomSession with Runtime.OpenSession. Trusted host code can use SessionForParticipant with a known participant ID. Do not derive that ID directly from an untrusted request and treat it as authentication.

Runtime and Store are privileged host interfaces. Handing them to an untrusted client or integration bypasses the purpose of the participant boundary. Room agents have their own participant identity and their returned publications are re-authorized; use that path for event-driven embedded work.

Search the documentation

Type to search all guides.

Diagram

100%Open original ↗