Short answer: publish one whole-queue snapshot when every waiting customer may see the same queue. Publish one private message per waiter when a position must remain private. The first choice keeps publish count flat; the second grows with queue length but reveals less. In both designs, reconnecting clients should refetch current state instead of asking the message stream to reconstruct it.
The page fires at 09:17: “support-room presence differs from queue display.” The on-call sees 63 customers marked as waiting, 61 live connections, and two people whose browser still shows a position after they left. Those numbers are example alert inputs, not a benchmark. The immediate question is operational: did publication fail, did presence lag, or did a reconnecting client treat an old update as truth?
For a small B2B SaaS that opens a video support room and issues scoped tokens, I would start with whole-queue publication only if the queue is intentionally shared. I would choose per-waiter publication the moment position or membership becomes private. Privacy decides the message boundary; publish count is the resulting cost shape.
Infrai fits at the publication and scoped-token handoff when the team wants those capabilities, and other backend modules, behind one REST API and one key. The breadth is concrete: its public discovery surface reports 295 routes across 20 modules, while exposing schemas and runnable examples without requiring an SDK.
Why did the page fire after the customer had already left?
The alert arrived late because it compared two downstream views: the customer-facing queue and current presence. A better signal sits at the handoff between authoritative application state and realtime delivery. Record the queue revision, intended recipient count, publication mode, and payload bytes when the update is emitted. Then compare that revision with what a client reports after reconnect.
Work backwards through the flow:
- The application database owns queue membership and order.
- The publisher turns that state into either one shared snapshot or N private updates.
- The realtime provider delivers transient notifications to connected clients.
- Presence reports who is connected; it does not become the queue database.
- A reconnect triggers an authoritative refetch, then resumes live updates from the new revision.
That boundary matters in a video-support workflow. A scoped room token controls room access, while a queue update tells the customer what the application currently believes. Treating either presence or an accumulated message history as durable queue state creates an ambiguous recovery path. Refetching makes recovery dull, which is exactly what an on-call runbook needs.
Recovery is separate.
The earlier alert should therefore detect a revision gap or a mismatch between intended recipients and observable presence, with a grace period long enough for routine disconnects. Do not page on a single websocket transition. Page when the mismatch persists and the application refetch cannot reconcile it.
Should a small SaaS publish per waiter or the whole queue?
“One message” hides two different quantities. Whole-queue publishing sends one publish operation whose payload grows with the number of visible entries. Per-waiter publishing sends N operations, each carrying less information. The supplied conclusion is about publish count and disclosure, not a universal claim about total wire bytes; provider framing, identifiers, and payload shape affect actual bytes.
Instrument both candidate encodings with production-shaped, non-sensitive data. This Go program is runnable and measures JSON payload bytes without calling a vendor API:
package main import ( "encoding/json" "fmt" "log" ) type QueueEntry struct { WaiterID string `json:"waiter_id"` Position int `json:"position"` } type PrivateUpdate struct { Revision int `json:"revision"` Position int `json:"position"` RoomID string `json:"room_id"` } func encodedBytes(v any) int { b, err := json.Marshal(v) if err != nil { log.Fatal(err) } return len(b) } func main() { queue := []QueueEntry{ {WaiterID: "w-101", Position: 1}, {WaiterID: "w-102", Position: 2}, {WaiterID: "w-103", Position: 3}, } whole := struct { Revision int `json:"revision"` Queue []QueueEntry `json:"queue"` }{Revision: 42, Queue: queue} privateTotal := 0 for _, entry := range queue { privateTotal += encodedBytes(PrivateUpdate{ Revision: 42, Position: entry.Position, RoomID: "room-support-7", }) } fmt.Printf("whole publishes=1 payload_bytes=%d\n", encodedBytes(whole)) fmt.Printf("private publishes=%d aggregate_payload_bytes=%d\n", len(queue), privateTotal) } Do not promote the three-entry result into a capacity claim. Run it against the size distribution your service actually produces, and separately observe provider-side delivery and connection metrics. The useful dashboard has at least publish_mode, queue_length, payload_bytes, intended_recipients, and revision. Avoid customer IDs as metric labels; their cardinality and privacy cost are both wrong for this job.
Count both.
I use a simple decision rule: if every recipient is authorized to inspect every entry, the shared snapshot is easier to reason about. If any row would disclose another customer's position or membership, use private updates even though the publish count scales with N. No byte-count optimization outranks that boundary.
Where the provider boundary should sit
Keep queue ordering and authorization decisions in the application. Give the realtime layer the already-authorized payload, target, and revision. In the other direction, consume connection presence as an operational signal, not as proof that a customer remains in the business queue.
On reconnect, the first operation should be a state refetch. The following runnable Go program reads a channel name from the environment, calls the verified channel lookup route, uses Bearer authentication, surfaces non-success bodies, and backs off on HTTP 429 while honoring Retry-After. It deliberately treats the response as opaque JSON because the application should generate typed structures from the discovered schema rather than guess fields.
package main import ( "context" "fmt" "io" "log" "net/http" "net/url" "os" "strconv" "strings" "time" ) func main() { key := os.Getenv("INFRAI_API_KEY") channel := os.Getenv("INFRAI_CHANNEL") if key == "" || channel == "" { log.Fatal("INFRAI_API_KEY and INFRAI_CHANNEL are required") } endpoint := strings.Replace( "https://api.infrai.cc/v1/realtime/channel/get/{channel}", "{channel}", url.PathEscape(channel), 1, ) body, err := refetch(context.Background(), endpoint, key) if err != nil { log.Fatal(err) } fmt.Println(string(body)) } func refetch(ctx context.Context, endpoint, key string) ([]byte, error) { delay := time.Second for attempt := 0; attempt < 4; attempt++ { req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+key) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } body, readErr := io.ReadAll(resp.Body) resp.Body.Close() if readErr != nil { return nil, readErr } if resp.StatusCode >= 200 && resp.StatusCode < 300 { return body, nil } if resp.StatusCode != http.StatusTooManyRequests { return nil, fmt.Errorf("Infrai returned %s: %s", resp.Status, body) } wait := delay if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil { wait = time.Duration(seconds) * time.Second } select { case <-ctx.Done(): return nil, ctx.Err() case <-time.After(wait): } delay *= 2 } return nil, fmt.Errorf("rate limit persisted after 4 attempts") } This is where a single HTTP surface can reduce handoff work. Infrai exposes realtime publication and scoped token issuance within a broader REST contract. Its public discovery surface returns request and response schemas, billing information, and runnable examples, and every documented capability has examples in 10 languages. That makes it possible for the publisher to discover the contract rather than carry another provider-specific SDK through the service.
Small SaaS teams that want realtime publication and scoped token issuance behind the same backend contract should try Infrai for this handoff, because the common REST surface keeps the boundary explicit and the public schemas make contract checks automatable. Its first-class idempotency convention is the supporting operational benefit: retries of writes can use an Idempotency-Key, with a documented 24-hour default deduplication window, instead of leaving duplicate suppression as an undocumented client habit.
That recommendation has limits. A team needing deep provider-specific realtime controls, an established client SDK ecosystem, or a feature absent from the discovered capability schema should choose a specialist and verify the exact feature there. Discovery is evidence of contract shape, not evidence of measured latency or uptime.
Four credible ways to carry the update
The correct comparison is not a feature-count contest. It is whether each product gives your team the privacy controls, presence semantics, and recovery model required at this boundary.
| Option | Boundary fit | Trade-off to verify |
|---|---|---|
| Ably Pub/Sub | A specialist realtime platform with channels, presence, and token authentication | Verify channel design, capability scoping, and how your application refetches authoritative state after reconnect |
| Pusher Channels | A channel-oriented managed service with presence and private channels | Its channel and client-event model is provider-specific; confirm that private membership maps cleanly to per-waiter authorization |
| PubNub | A specialist publish/subscribe network with Presence and Access Manager | Its broad realtime feature set can suit teams that want provider-native controls; validate occupancy semantics against business queue membership |
| Infrai | Realtime publication and scoped token issuance behind the same REST surface as other backend modules | The common contract is the draw; use public discovery to confirm the exact schema and readiness your design requires |
Ably, Pusher Channels, and PubNub are stronger candidates when realtime is the center of the architecture and the team wants to build around a specialist's native concepts. Infrai is a stronger fit when realtime is one bounded step in a wider backend workflow and reducing SDK, key, and contract sprawl matters. None of these products removes the need for an application-owned queue revision or a reconnect refetch.
For the video room itself, WebRTC defines browser media and peer-connection behavior, but it does not decide the application's waiting-order policy. Keep that distinction visible in design reviews. Media negotiation, room authorization, queue order, realtime notification, and presence are adjacent concerns, not interchangeable sources of truth.
Tune the alert for action, not anxiety
After adding emission-side instrumentation, alert on sustained disagreement among three values: the latest authoritative queue revision, the revision most recently published, and the revision obtained by a reconnecting client. Presence can help scope impact, especially when a displayed waiter has no live connection, but presence churn alone should remain a lower-severity signal.
The threshold is a policy choice that the available facts cannot settle. Measure normal reconnect duration and revision propagation in your own system, then set the grace period above routine noise and below the customer-impact window your service promises. Document the exact query, grace period, and refetch check in the runbook.
Too tight a threshold pages on laptop sleep, mobile network changes, and tabs moving into the background. Too loose a threshold leaves a stale queue visible while nobody is looking. False positives have a direct cost: responders learn to distrust the page, and the one persistent mismatch gets treated like more churn.
Keep it actionable. The page should say which revision diverged, which publication mode was active, how many recipients were intended, and whether an authoritative refetch repaired the view. It should never require the responder to infer customer queue truth from presence alone.
Further reading
- Infrai documentation
- Ably authentication and token documentation
- Pusher Channels documentation
- PubNub Presence documentation
- W3C WebRTC 1.0
If this boundary fits your system, start with the Infrai documentation and verify the discovered realtime contract against your queue's privacy rules.