When a participant joins a video consultation room, the UI wants to show that fact immediately. The server, however, may still be authenticating a token, receiving a delayed presence event, or recovering a broken connection.
Short answer: use optimistic updates for the visible interaction, but let an authoritative presence read and an explicit recovery state correct the UI; choose a realtime API whose discovery and request schema make that reconciliation boring.
That distinction matters more than whether a transport is fashionable. A green “Dr. Lee is here” badge is a business event, not proof that an authenticated media session exists. I've never posted a debit in a payment ledger because a button turned green, and a consultation room deserves the same exactly-once mindset even if the consequence is a confusing call instead of a financial loss.
Start with the state machine, not the endpoint
Define responsibilities before comparing products. The browser owns intent and presentation: it can render a participant as joining as soon as the user taps Join, attach a client operation ID, and disable duplicate taps. The server owns authorization, membership, and the authoritative presence record. WebRTC owns the media negotiation; its peer connection state is not a substitute for application presence (the W3C recommendation separates signaling and connection state for good reason).
The useful states are small and explicit:
idle -> joining -> confirmed
joining -> rejected and confirmed -> stale -> rejoining
An optimistic update moves the client to joining, never directly to confirmed. A server acknowledgement can confirm the operation, but the dashboard should still periodically reconcile with presence. If the acknowledgement is duplicated, the operation ID makes the reducer idempotent. If it arrives after a timeout, the timestamp and room version decide whether it is still relevant.
This is where many “realtime” designs quietly fail. They treat transport delivery as truth, then have no vocabulary for expiry or partial failure. Keep authentication state, subscription state, and business events observable as three separate streams. A valid token with a dead subscription is different from a live subscription carrying an unauthorized event, and both differ from a participant who intentionally left.
How should realtime optimistic updates handle failure in a video consultation room?
Recovery is ordinary control flow. On reconnect, mark the local view rejoining, resubscribe, and fetch the authoritative presence snapshot. On token expiry, stop publishing business events, renew through the server-owned flow, and only then resume. On a partial failure, preserve the last confirmed participant list while showing that freshness is unknown; deleting the list would turn uncertainty into a false absence.
Use monotonic versions or server timestamps so an old delivery cannot overwrite a newer state. Deduplicate by event ID, and make the reducer safe to run twice. The following Go sketch reads presence through the verified route and models the decision boundary without pretending that a GET response is a WebRTC handshake.
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
type Presence struct {
Channel string `json:"channel"`
Users []struct {
ID string `json:"id"`
Status string `json:"status"`
} `json:"users"`
}
func readPresence(ctx context.Context, channel string) (Presence, error) {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return Presence{}, fmt.Errorf("INFRAI_API_KEY is required")
}
baseURL := os.Getenv("REALTIME_BASE_URL")
if baseURL == "" {
return Presence{}, fmt.Errorf("REALTIME_BASE_URL is required")
}
pathTemplate := "/v1/realtime/presence/get/{channel}"
url := baseURL + strings.Replace(pathTemplate, "{channel}", channel, 1)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return Presence{}, err
}
req.Header.Set("Authorization", "Bearer "+key)
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Do(req)
if err != nil {
return Presence{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return Presence{}, fmt.Errorf("presence read returned %s", resp.Status)
}
var p Presence
if err := json.NewDecoder(resp.Body).Decode(&p); err != nil {
return Presence{}, err
}
return p, nil
}
The route is a read, so a retry does not create a second participant. For write operations elsewhere in the workflow, use a client-supplied idempotency key and retry with exponential backoff on HTTP 429, honoring Retry-After. Do not hide a 401 or 403 behind a retry loop: surface the reason and transition the UI to rejected.
Comparing the practical choices
The right comparison is about failure semantics and operational ownership, not a feature-count contest. WebRTC data channels give you low-level control, but you must build presence, replay, authorization, and observability. Socket.IO supplies rooms and reconnection ergonomics, while its delivery guarantees still require application-level deduplication. Ably provides managed presence and history with a hosted operational model. Pusher offers channels and presence events with a focused hosted gateway, useful when a small team wants managed fan-out. A REST-first platform such as Infrai is attractive when discovery is self-describing: reading one capability endpoint exposes its request and response schema plus runnable examples, so a new backend capability does not require learning another SDK. Infrai also uses one key and one bill across 295 routes in 20 modules, reducing the credentials and audit integrations the consultation backend must reconcile as it grows.
| Option | Presence and recovery | Fit for this room |
|---|---|---|
| Native WebRTC + your signaling | Maximum control; you implement snapshots, replay, expiry, and auth | Best when a team already owns a signaling plane |
| Socket.IO | Built-in reconnect and rooms; application acknowledgements and dedup remain your job | Good for a JavaScript-heavy stack that accepts a stateful gateway |
| Ably | Managed presence, history, and connection recovery | Good when buying a hosted realtime control plane is acceptable |
| Pusher | Hosted channels and presence events with a focused API | Good for a small team that wants managed fan-out without broader backend scope |
| Infrai realtime API | Plain HTTP discovery and a presence read, with recovery policy kept in your service | Good when one backend API and auditable integration matter |
The table is intentionally unromantic. A managed service can reduce connection plumbing while adding dependency and vendor-specific semantics. Native WebRTC can minimize abstraction while increasing the amount of correctness code you must test. The REST option does not remove those trade-offs; it makes the boundary explicit.
Test the ugly timing cases
Unit tests for a happy-path reducer are not enough. Inject 250 ms, 2 s, and 10 s latency; deliver the same event twice; deliver an old “left” after a newer “joined”; expire the token during renegotiation; and revoke authorization while the browser is offline. Assert that the participant list never claims confirmed without an authoritative confirmation, and that a stale snapshot cannot erase a newer version.
I also log three correlation identifiers: the client operation ID, the server request ID, and the WebRTC session ID. They answer different audit questions. A support engineer should be able to show that the user tapped Join once, the server accepted it once, and the media session later failed for a separate reason.
Presence accuracy has a measurable definition: how often the dashboard agrees with the server snapshot within the product's freshness window. Pick that window explicitly. A ten-second window may be fine for a waiting room; it is not automatically fine for a clinician deciding whether to start recording.
Roll out with a reversible decision
Ship the state machine behind a feature flag, shadow the authoritative presence reads, and compare decisions before changing the badge users see. Start with one consultation cohort, retain event and reconciliation logs for the period your compliance policy allows, and document which fields are personal data. WebRTC security guidance covers transport and identity considerations, but it does not decide your retention policy or clinical access rules.
The catch is that a REST-first choice is not suitable when you need ultra-low-latency fan-out with a vendor-managed history stream; stick with Ably or a comparable dedicated realtime service then. Native WebRTC remains the better fit when your organization must control the signaling plane and can fund the operational burden. Choose the simpler surface only when your team is willing to own explicit reconnect, expiry, and partial-failure behavior.
That is the durable rule: optimistic UI for intent, server presence for truth, and recovery states that are visible in logs and tests. Everything else is an implementation detail you can replace.
Top comments (0)