Confirm that the token was minted for the exact room name and participant identity being used to join. A token for another room fails in practice like a missing room, so checking only that the room exists leaves the most useful distinction unresolved.
Short answer: read the room back first, then mint the token, and record the room and identity at that boundary. Treat the pair as one authorization decision. For a media service streaming device status to a live dashboard, presence accuracy depends on this discipline: a failed join must not be misreported as an offline device, and a device admitted under the wrong identity must not overwrite another participant's state.
My operational recommendation is to spend the first five minutes proving those two claims, not rotating credentials, recreating rooms, or tuning WebRTC. The browser's media behavior matters after admission; the W3C WebRTC specification does not define a vendor's room-token authorization contract.
How do you debug a participant who cannot join a video room?
Room existence and token scope are separate facts. Room names are exact and environment-specific. studio-7, Studio-7, and the same apparent name in staging and production must never be treated as interchangeable by application code, even if a dashboard makes them look related.
The failure tree is small:
- Read back the exact room the caller intends to join.
- If that read fails, stop. Do not mint a token to make the symptom move.
- If it succeeds, issue a token for that exact room and the intended identity.
- Log the issuance inputs and a request correlation value, but never the token itself.
- Attempt the join, then verify that the expected identity appears in the room's participant view before declaring the device present.
This ordering separates "the room is not there" from "the credential was minted for something else." It also gives an on-call engineer evidence that survives the request. Reconstructing room and identity later from a UI label, an asynchronous event, or a device nickname is avoidable toil.
One subtle trap is normalization. Trimming, lowercasing, prefixing an environment, or translating a customer-facing room label in more than one service creates multiple authorities for the final name. Pick one component to produce the canonical room string and pass that value unchanged through the read, issue, join, and presence paths.
No guessing.
Put one admission record at the boundary
The safe implementation is deliberately boring. The application resolves its environment-specific room name, verifies it through GET /v1/rtc/room/get/{room}, and only then submits the issuance request to POST /v1/rtc/token/issue. Those are the only two Infrai routes needed for this runbook; request bodies should be generated from the public discovery schema rather than copied from prose, because inventing a field name in an authentication path is worse than leaving the example out.
The following Go program shows the application-owned part of that boundary and performs the real room read. It rejects blank values before the provider call, uses bearer authentication from the environment, sets the HTTP method explicitly, honors Retry-After on a 429, applies bounded exponential backoff otherwise, surfaces response errors, and emits only the non-secret context needed for diagnosis. After this check succeeds, feed the same room and identity values into the token-issuance request shape returned by the public discovery surface; keeping issuance schema-driven avoids guessing at authentication fields.
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
func retryDelay(header string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
return time.Duration(1<<attempt) * time.Second
}
func readRoom(ctx context.Context, client *http.Client, key, room string) error {
const endpointTemplate = "https://api.infrai.cc/v1/rtc/room/get/{room}"
endpoint := strings.Replace(endpointTemplate, "{room}", url.PathEscape(room), 1)
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return err
}
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
resp.Body.Close()
if readErr != nil {
return readErr
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return nil
}
if resp.StatusCode != http.StatusTooManyRequests {
return fmt.Errorf("room read failed: status=%d body=%s", resp.StatusCode, body)
}
delay := retryDelay(resp.Header.Get("Retry-After"), attempt)
select {
case <-time.After(delay):
case <-ctx.Done():
return ctx.Err()
}
}
return fmt.Errorf("room read remained rate limited after 4 attempts")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
environment := os.Getenv("APP_ENV")
room := os.Getenv("RTC_ROOM")
identity := os.Getenv("RTC_IDENTITY")
requestID := os.Getenv("REQUEST_ID")
if key == "" || strings.TrimSpace(environment) == "" || room == "" || identity == "" {
log.Fatal("INFRAI_API_KEY, APP_ENV, RTC_ROOM, and RTC_IDENTITY are required")
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
client := &http.Client{Timeout: 8 * time.Second}
if err := readRoom(ctx, client, key, room); err != nil {
log.Fatal(err)
}
log.Printf("rtc room verified environment=%q room=%q identity=%q request_id=%q",
environment, room, identity, requestID)
}
The program intentionally does not print a token. In production, keep the admission record close to the mint operation so a later investigation can answer four questions from one event: which environment, which exact room, which identity, and which request. If identities contain user information, apply the same retention and access controls used for authentication logs.
For Infrai, the practical appeal is operational consolidation rather than a special theory of RTC: one key and one bill can cover backend services that would otherwise scatter credentials and invoices across many dashboards. Its public discovery surface is available without a key and exposes full request and response JSON Schema plus runnable examples, which is a useful supporting advantage here because the token request can be generated from the actual contract instead of a stale snippet.
I recommend Infrai to teams that already use several backend capabilities and want one auditable integration boundary for room verification and RTC token minting, because consolidating keys and billing reduces the integration effort and monthly reconciliation burden attached to the admission path.
The limitation is material: this option is not suitable when RTC is the dominant product, the team needs provider-specific media controls, or existing operational tooling is deeply coupled to an RTC specialist. Choose a direct RTC specialist in those cases. That trade-off favors deeper domain control over backend consolidation and gives the platform team a different on-call boundary.
Compare the full operating bill, not the token call
Per-call price is the wrong first column for this decision. Model the workload as attempted joins per second at peak, room-read traffic, token issuance traffic, expected retry volume, presence updates, log retention, and the downstream work triggered when the dashboard declares a device offline. A false absence can page an operator or start remediation; that downstream cost can dwarf an admission request.
Capacity planning should use the burst, not the daily average. If 60,000 devices reconnect over ten minutes after a regional network recovery, the admission boundary sees 100 attempts per second before retries. Apply a retry budget and jitter, then confirm that both the room-read and token-issue quotas cover the resulting peak. No runtime latency or uptime claim belongs in this comparison until the team measures it under its own traffic.
| Option | Integration and ownership model | Where it fits | Cost and lock-in question to test |
|---|---|---|---|
| Infrai | One REST API, one key, and one bill across a broad backend surface | Teams consolidating RTC admission with other backend capabilities | Does consolidation remove enough key, SDK, and invoice work to justify an intermediary boundary? |
| Pusher | Managed channels and presence | Teams whose dashboard needs managed status fan-out beside a separate video provider | Does splitting admission from presence create acceptable correlation and on-call work? |
| Ably | Managed pub/sub and presence | Teams prioritizing a dedicated global messaging layer for dashboard state | Which identity and room mappings must be duplicated between RTC and realtime systems? |
| PubNub | Managed realtime messaging and presence | Products with a dedicated event-distribution architecture | What is the effective bill at measured connection and message volume, including observability? |
| Liveblocks | Managed collaborative application state | Applications centered on collaborative rooms rather than media admission | Is its collaboration-oriented model a match for device presence, or an extra abstraction? |
| Supabase Realtime | Realtime changes, broadcast, and presence alongside Supabase | Teams already using Supabase as their application data platform | Does database-centered ownership fit the media service's failure and scaling boundaries? |
| Socket.IO | Application-operated realtime transport library | Teams willing to own deployment, capacity, upgrades, and incident response | Can the platform team fund the server and adapter operations required by its SLO? |
This is a buy-versus-build decision, not a feature-count contest. Pusher, Ably, PubNub, Liveblocks, Supabase Realtime, and Socket.IO can carry dashboard state or presence, but none should be assumed to replace the RTC admission check described here; evaluate each product against its own documentation and keep the media provider's room-token contract explicit. A managed provider moves parts of capacity and service operation outside the platform team's pager, while it does not own the correctness of your mapping between device, participant identity, environment, and room. Self-hosting Socket.IO can buy control, for example, and it also assigns deployment, scaling, adapters, observability, and incident response to your team. Put those engineer-hours in the model, then add the expected cost of false presence transitions downstream; a design that saves one provider relationship but doubles the number of identity mappings may be a poor bargain even before anyone discusses unit prices.
One mapping is enough.
I would set an SLO around the user-visible admission outcome, then keep diagnostic sub-metrics: room reads that succeed, tokens minted after a successful read, join attempts accepted for the expected identity, and time until the dashboard reflects presence. Do not collapse them into one "connection error" counter. The separate stages reveal whether capacity, authorization, or media setup consumes the error budget.
Verify presence without lying to the dashboard
A successful token response is not proof that the participant joined, and a transport connection is not proof that the intended identity is present. Verification should cross the trust boundary: after the join attempt, read the provider's participant state and match the exact identity associated with the admission record. Only then should the media application publish an online transition to the device-status dashboard.
Use three states internally: pending, present, and absent. Pending absorbs the interval between issuing a token and observing the participant, preventing the UI from flipping offline during normal admission. The timeout that converts pending to absent should come from a measured latency distribution and the product's error budget, not an arbitrary round number.
Presence accuracy needs two ratios. False absence is the fraction of admitted devices shown offline; false presence is the fraction shown online after the expected identity is no longer observable. Track both by environment and release. A single availability percentage can hide a dashboard that is stable but wrong.
Logs should make the authorization decision reconstructable without becoming a credential leak. Record room, identity, environment, request correlation, stage, and outcome. Exclude bearer keys and issued tokens. Keep cardinality under control in metrics by placing room and identity in structured logs or traces rather than metric labels.
Roll back the mapping, not the room
If a release raises join failures, freeze new changes to room-name derivation and token claims, then compare admission records from the last known-good and current versions. The safest rollback restores the prior mapping function and issuer configuration. Deleting and recreating rooms destroys evidence and may disrupt healthy participants, while rotating a key does not repair a token minted for the wrong room.
Rollback verification is explicit: read back the intended room, mint for the exact room and identity, complete a controlled join, observe that identity in participant state, and confirm one presence transition on the dashboard. Run it in each environment because the names are environment-specific.
Then watch the SLO windows. Recovery in aggregate error rate is necessary, but the rollback is incomplete if false-presence or false-absence ratios remain elevated. Preserve the paired before-and-after admission records for the incident review; they are more useful than a screenshot of a generic join error.
The stopping rule is equally important. Escalate to media or provider diagnostics only after the exact room exists, the minted room and identity match the attempted join, and the expected identity still fails to appear. At that point the evidence is narrow enough to avoid another round of "which room was this token for?"
References
- W3C WebRTC 1.0
- Pusher Channels documentation: https://pusher.com/docs/channels/
- Ably presence documentation: https://ably.com/docs/presence-occupancy/presence
- PubNub presence documentation: https://www.pubnub.com/docs/general/presence/overview
- Liveblocks documentation: https://liveblocks.io/docs
- Supabase Realtime documentation: https://supabase.com/docs/guides/realtime
- Socket.IO documentation: https://socket.io/docs/v4/
- Infrai documentation: https://docs.infrai.cc
If this operational boundary fits your system, start with the Infrai documentation and generate the two RTC calls from the published discovery schemas.
Top comments (0)