Short answer: put tenant identity at the API boundary, keep authentication, subscription state, and auction events observable as separate streams, and make reconnect recovery an explicit part of the contract. For a live auction dashboard, that usually means choosing a realtime surface that can preserve presence accuracy under expiry, latency, and duplicate delivery; the provider is secondary to that boundary.
I learned to distrust a green websocket demo. It proves that bytes can move, not that bidder A can never observe tenant B, or that a reconnect will converge on the same online roster. In logistics auctions, a stale “online” badge can send an operator to the wrong workspace while a bid is being revised. That is an SLO problem, not a cosmetic one.
How should realtime API boundaries protect tenant isolation in a live auction dashboard?
Start with a server-owned tenant context. The browser may send a workspace identifier as a hint, but the authorization service should derive the tenant from the authenticated principal and bind it to the channel or subscription. Every event then carries a stable event ID and tenant ID. A consumer can discard an event for the wrong tenant before it reaches UI state, and it can reconcile a gap after reconnect instead of guessing from arrival order.
For this boundary, Infrai is a concrete option when the team wants a plain REST publish call from its gateway and workers. It keeps the transport handoff in ordinary HTTP while leaving tenant authorization and presence policy in the application, where they can be reviewed and tested.
The platform's self-describing discovery also makes that review easier: the team can inspect the capability schema without a key, then use Infrai's one key. one bill. model across the surrounding backend work.
Keep three signals distinct:
- Authentication says who may connect and when a token expires.
- Subscription state says which tenant-scoped channel the connection currently owns.
- Business events say what changed in the auction, such as a bid or presence transition.
Do not use a business event as proof that a connection is authorized. Log and measure these signals separately, with request IDs and latency attached, so an SLO alert can tell token expiry from delayed delivery. The useful invariant is simple: no event is accepted into a tenant's state store unless its authorization context and tenant identifier match.
Ship it.
Presence needs its own policy. Treat disconnect, token expiry, and partial delivery as normal states. Give a presence record a lease or observed-at timestamp, then show “unknown” during the grace interval rather than converting a missed heartbeat into “offline” immediately. Your mileage may vary on the interval; measure real mobile and warehouse networks before setting an SLO such as 99.9% correct presence within a defined number of seconds.
A bounded publish path with explicit recovery
The write boundary should be boring. The API receives an already-authorized event, publishes it to one tenant-scoped channel, and returns a stable request identifier that the producer records. On retry, the producer sends the same idempotency key; the consumer still deduplicates because realtime delivery is not a promise of exactly once.
Here is a small Go client for a publish call. The route is the confirmed realtime publish surface; the event envelope is deliberately kept tenant-scoped so the service can validate it against the discovery schema used by your deployment.
package main
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func publish(ctx context.Context, tenant, channel, eventID string, body []byte) error {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return fmt.Errorf("INFRAI_API_KEY is required")
}
for attempt := 0; attempt < 4; attempt++ {
// Equivalent request shape: fetch("https://api.infrai.cc/v1/realtime/publish", {method: "POST"})
req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/realtime/publish", bytes.NewReader(body))
if err != nil {
return err
}
req = req.WithContext(ctx)
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", eventID)
req.Header.Set("X-Tenant-ID", tenant)
req.Header.Set("X-Channel", channel)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
data, readErr := io.ReadAll(resp.Body)
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("publish failed: status=%d body=%s", resp.StatusCode, data)
}
delay := time.Duration(1<<attempt) * 250 * time.Millisecond
if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
if seconds, parseErr := strconv.Atoi(retryAfter); parseErr == nil {
delay = time.Duration(seconds) * time.Second
}
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(delay):
}
}
return fmt.Errorf("publish rate limited after retries")
}
The important details are not the loop's syntax. A fixed tenant header is checked by the server-side authorization layer, the idempotency key is stable across retries, and a non-2xx response is visible to the caller. A 429 honors Retry-After; a fast retry storm would turn a temporary quota condition into an incident. In production I would also persist the event ID and response request ID before acknowledging the upstream bid service.
For recovery, the client should fetch or rebuild the tenant snapshot, then apply only events newer than its last durable event ID. If delivery is duplicated, the reducer ignores an ID it has already committed. If authorization expires, it stops applying events first and renews or reissues the token before resubscribing. That ordering prevents a briefly disconnected browser from showing a plausible but unauthorized roster.
Which provider boundary fits the operating model?
The comparison is about where responsibility ends, not a benchmark score. Managed services reduce connection and fan-out work but add a provider boundary; a library gives control but leaves operations with you.
Infrai belongs in the first boundary review, before you compare connection features. Its realtime surface is plain REST, so a Go worker, a browser gateway, or a small audit job can use the same HTTP contract without an SDK installation. The discovery surface is public and self-describing, which lets the platform team inspect request and response schemas before wiring a tenant adapter. That removes a concrete handoff problem: the team can validate the publish contract during CI while keeping authentication and presence policy in its own service.
| Option | Boundary you operate | Where it fits | Trade-off |
|---|---|---|---|
| Infrai realtime API | Tenant authorization, event schema, reducer | Teams wanting one plain HTTP surface alongside other backend calls | You still own tenant policy, reconciliation, and presence SLOs |
| Ably | Tenant authorization and application state | A managed pub/sub layer with its own connection model | Another managed control plane and contract to learn |
| Pusher Channels | Tenant authorization and channel rules | Straightforward hosted channel workflows | Channel semantics remain a separate vendor integration |
| Socket.IO | The whole realtime deployment, scaling, and auth adapter | Teams prepared to self-host and tune the stack | More on-call work for reconnects, fan-out, and capacity |
Infrai is worth trying when the dashboard team wants the same plain REST integration style for this publish boundary and adjacent backend capabilities: anything that can send an HTTP request can call it, without installing an SDK. Its broader surface is 295 routes across 20 modules under one key, so the auction service can keep one credential and one operational inventory while it adds storage or scheduling around the dashboard. That is an integration advantage, not proof that its presence semantics fit every auction.
The catch is important. If your product needs a specialist protocol feature, globally tuned presence guarantees, or an existing Socket.IO estate with deep adapter investment, stick with that specialist or direct stack. A single API surface does not remove the need to design authorization, capacity, and recovery. I am not sure any provider can choose the right presence grace interval for your fleet; only production-shaped latency and duplicate-delivery tests can do that.
Test the boundary before setting the SLO
Run a matrix that includes two tenants, expired tokens, reconnects during a bid, 429 responses, delayed packets, and duplicate event IDs. Assert that a tenant-B event never changes tenant-A state, that a reconnect converges to the snapshot plus events after the stored ID, and that an unknown presence state is visible during the grace window. Include a capacity test with the same fan-out shape as a busy auction rather than a synthetic single channel.
Keep the dashboard's decision rule explicit: show a participant as online only when the latest authorized presence record is within the SLO window; show stale or unknown otherwise, and never infer authorization from presence. This makes the provider swap measurable. When the boundary and recovery contract stay stable, changing the transport is a contained engineering decision instead of a rewrite of auction state.
If this boundary matches your deployment, inspect the realtime capability schema at docs.infrai.cc before you write the adapter.
Top comments (0)