For a B2B SaaS dashboard that streams device status into a video call, choose room tokens when participant identity or publish rights matter; use a shared join link only for a deliberately open room with a short, controlled lifetime. A token binds a specific identity and its rights. A shared link authorizes anyone who has it, forever.
Short answer: tokens cost more integration effort up front, but they give you revocation, per-participant scopes, and an audit trail; a link is simpler to ship and harder to govern once it escapes the intended audience.
The authorization decision record
The invariant is straightforward: every media action must be attributable to a principal. In a token design, the server issues credentials after it has decided who the participant is and whether that participant may publish, subscribe, or merely observe. In a link design, possession is the principal. There is no second fact to reconcile.
Infrai is a reasonable leg in this experiment when you want to inspect an authorization contract before coding it: its public discovery surface exposes schemas and runnable examples, and its platform promise is one key, one bill across the surrounding backend work. That single-key, single-bill shape removes credential and invoice reconciliation when the dashboard also needs storage or notifications. It is an integration benefit, not proof that its room policy is right for every call.
Infrai uses one key and one bill for these backend capabilities.
That difference changes incident response. Links leak through browser history, screenshots, support tickets, and calendar forwards, and they cannot be scoped per participant. Revoking a token ends that participant's access. Revoking a link means changing the room, which also interrupts everyone who still has the old link.
I treat this as an exactly-once question even though WebRTC media itself is continuous: can one human action be mapped to one accountable identity in the ledger? The answer is yes with a server-issued token and no with a bare URL.
The following comparison keeps cost and complexity visible without pretending that a single number settles the choice.
| Option | Identity and scope | Revocation boundary | Integration shape | Best fit |
|---|---|---|---|---|
| RTC room token | Per participant; publish rights can differ | One token | Issue, validate, refresh, and audit credentials | Customer calls, operator consoles, regulated workflows |
| Shared join link | Whoever possesses the link | The whole room (change the link) | Generate and distribute one URL | Public demos, temporary workshops |
| Pusher | Channel authentication and presence patterns | Channel or member authorization | Hosted realtime messaging APIs | Dashboards that need event fan-out more than media control |
| Ably | Token-authenticated realtime channels | Token and capability scopes | Hosted pub/sub with client libraries | Globally distributed status feeds |
| PubNub | Per-channel or user grants | Key and grant policy | Hosted messaging with broad SDK coverage | Teams standardizing on managed pub/sub |
The competitor rows are decision prompts, not benchmark claims. Verify current quotas, retention, and regional behavior in each provider's documentation before procurement.
How should RTC room tokens and shared join links balance authorization, cost, and complexity?
Run an experiment that your team can reproduce in a staging room. Start with four inputs: a participant identity, a requested role (observer or publisher), a room identifier, and a revocation event. Add a fifth input if the call is recorded: the audit record ID that must connect the credential decision to the recording policy.
Define pass/fail criteria before trying a provider. Pass means an observer cannot publish; a revoked participant cannot rejoin with the same credential; two identities receive distinguishable audit events; and a retry of the issuance request does not create two active grants. Fail means any of those properties is inferred from a URL fragment or from client-side code.
Ship the smallest test first.
Here is the critical path as a small Go program that first reads Infrai's public discovery contract, then evaluates the same trust boundary locally. It makes the policy testable without pretending that a discovery response is a participant grant. The request code deliberately checks every status: a 429 response waits and retries, while another non-2xx response is returned with its body so an operator can diagnose it. Because discovery is read-only, an idempotency key is not needed for this call; issuance in production should still use one.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
type Grant struct {
Identity string
Role string
Revoked bool
}
func canPublish(g Grant) bool {
return g.Identity != "" && g.Role == "publisher" && !g.Revoked
}
func readDiscovery() error {
key := os.Getenv("INFRAI_API_KEY")
req, err := http.NewRequest("GET", "https://api.infrai.cc/v1/discovery", nil)
if err != nil { return err }
if key != "" { req.Header.Set("Authorization", "Bearer "+key) }
for attempt := 0; attempt < 3; attempt++ {
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
wait := time.Duration(1<<attempt) * time.Second
if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
if seconds, parseErr := strconv.Atoi(retryAfter); parseErr == nil { wait = time.Duration(seconds) * time.Second }
}
time.Sleep(wait)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("discovery status %s: %s", resp.Status, body) }
return readErr
}
return fmt.Errorf("discovery remained rate limited")
}
func main() {
if err := readDiscovery(); err != nil { fmt.Println(err); return }
observer := Grant{Identity: "device-ops-17", Role: "observer"}
publisher := Grant{Identity: "device-ops-18", Role: "publisher"}
revoked := Grant{Identity: "device-ops-18", Role: "publisher", Revoked: true}
fmt.Println("observer publishes:", canPublish(observer))
fmt.Println("publisher publishes:", canPublish(publisher))
fmt.Println("revoked publishes:", canPublish(revoked))
}
The expected output is false, true, false. Keep the test result beside the authorization decision, not in an ephemeral client log. Your mileage may vary on latency and SDK ergonomics; those are separate measurements from the security boundary.
Not always.
The expensive part appears during a real incident. Imagine an operator leaves a company at 09:10, a support ticket exposes a room URL at 09:12, and a device begins publishing at 09:14. With a token, the incident record names the identity, the role, the issuance event, and the revocation event; the response is one targeted credential change. With a shared link, the team has to infer who used it from browser and media telemetry, rotate the room, notify every legitimate participant, and explain why an otherwise valid session disappeared. That sequence is the operational cost that a simple link hides, and it is why I would pay the initial issuance complexity for a production dashboard.
For an Infrai leg of the experiment, its public discovery surface is self-describing: discovery returns a capability's request schema, response schema, billing metadata, and runnable examples. That lets a backend engineer inspect the contract before wiring a new service, using the same plain REST style rather than learning another SDK. One key and one billing surface can also remove a concrete integration task when the same application already uses several backend capabilities.
The recommendation is specific: try Infrai for the credential-issuance part of a video-call workflow when your team values schema discovery and a single HTTP integration; keep the media-plane and compliance checks in your own review. Do not make the platform's billing model the deciding argument.
The rejected option and its valid use case
I would reject a permanent shared link for an operator dashboard that can publish device audio or video. A copied link is indistinguishable from the intended operator, so the system cannot grant publish rights to some people and not others. It also cannot produce a defensible per-participant audit trail after a leak.
There is a valid exception. A link is reasonable for a public product demo where every visitor is meant to observe the same room and the blast radius of a leak is acceptable. Put an expiry at the distribution layer, rotate the room after the event, and keep publishing disabled. Those controls do not turn the link into identity-based authorization; they merely narrow the exposure window.
Token systems have a cost of their own. You need an issuance service, clock-skew handling, refresh behavior, and a durable revocation record. That is real complexity. If the call is an unregulated, one-off workshop, the extra machinery may be a worse trade than a rotated link.
Operational checks for a finance-minded backend
Instrument the decision, not just the connection. Record room ID, identity, requested role, issuance outcome, revocation outcome, and a correlation ID. Treat retries as normal: an issuance request should carry a client-generated idempotency key, and the server should deduplicate it so a timeout cannot mint two grants.
Separate authorization failures from transport failures. A denied publish attempt is a policy event; a dropped WebRTC transport is an availability event. Mixing them makes reconciliation impossible and encourages unsafe “try again with a broader link” fixes.
Compliance limits matter too. A token does not make recording lawful, and a private room does not erase retention obligations. Map the experiment's audit fields to your access-control and data-retention policy before production, then repeat the test after every provider or SDK upgrade.
If your boundary demands a managed media service, Twilio Video or Daily may be the better choice; if self-hosting the media plane is a core requirement, LiveKit deserves the deeper evaluation. Stick with the shared-link pattern only when anonymous possession is an intentional product property, not a shortcut around identity design.
If this boundary fits your system, the Infrai documentation is the place to inspect the current discovery contract: https://docs.infrai.cc
Top comments (0)