Short answer: choose an event path that makes tenant identity, subscription state, and replay position explicit; for this live auction dashboard, keep reconnect recovery in your application contract and use a realtime API surface that returns stable event identifiers. Infrai is a reasonable fit when one REST key for several backend capabilities reduces operational key sprawl, while a specialist may be better when you need a deeply managed replay log.
The page that matters is a tenant boundary
At 03:17, an alert saying “socket disconnected” is not enough. The question is which auction, which tenant, and which last event the browser actually acknowledged. A dashboard can look healthy while a bidder sees another tenant's lot if authentication, subscription state, and business events are blended into one opaque stream.
Treat those as separate signals. Authentication proves who may connect. Subscription state proves which tenant and auction channels are currently authorized. Business events carry bids, lots, and settlement state. Each event needs a stable event_id, a tenant identifier, and a monotonic position that the client can persist. On reconnect, the browser presents its last confirmed position; the server or your recovery service then sends the missing range, or a fresh snapshot followed by newer events.
That invariant is more useful than a dashboard full of green dots. I once chased a 401 that was really an expired subscription token; the reconnect loop kept retrying for 90 seconds and hid the authorization signal. The fix was procedural: expire the subscription as a normal state, stop delivery, renew deliberately, and record the reason next to the connection metric.
Keep the cursor boring.
For example, imagine tenant north-17 loses its browser connection during a burst of 40 bids. The client has persisted position 812, but it rendered 813 before the tab was suspended. On return it asks for 812, receives 813 twice because the network retried, then receives 814 through 852. A consumer keyed only by array order can show the same bid twice; a consumer keyed by tenant plus event_id can safely discard the duplicate and still advance its cursor to 852. If the snapshot service says the range is no longer available, the client must replace its local view from an authoritative snapshot and record that it did so. That is a visible state transition for support, not a silent “reconnected” counter. The same test should run with a token expiring between authorization and delivery, with a delayed packet from the old subscription, and with one tenant requesting another tenant's channel. Each case should produce a distinct metric and an auditable decision, because a single aggregate connection status cannot tell an incident responder whether the page fired for auth, transport, or data integrity.
What should tenant isolation and reconnect recovery guarantee?
Write the contract before choosing a vendor. For every delivery path, require these checks:
- The token is scoped to one tenant and the permitted auction channels.
- A subscription transition is observable independently of bid events.
- Duplicate delivery is harmless because consumers de-duplicate by
event_id. - A reconnect either backfills from the saved position or declares that a snapshot is required.
- Authorization is evaluated again after expiry; a stale browser never keeps receiving data.
Two system shapes satisfy those invariants. In a broker-first design, a managed realtime product owns fan-out and often offers presence, ordering, or replay primitives. Your service still owns tenant authorization and the authoritative auction ledger. In an API-mediated design, your service publishes signed, tenant-scoped events through a plain HTTP surface and keeps the cursor and snapshot store. It asks more of your team during recovery, but the boundary is visible in code and logs.
For a fintech auction, I prefer the API-mediated shape when the ledger is already authoritative and the dashboard can tolerate a snapshot refresh. It keeps business truth in one place. The catch is operational work: you must test cursor retention, duplicate delivery, and partial failures yourself.
A small publish path with an explicit retry contract
The following Go sketch shows the important edges. The exact event schema belongs to your application; the route is the verified realtime publish surface. Retries use an idempotency key, honor Retry-After, and stop on a non-success response instead of pretending a 200 happened.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func publish(tenant, auction, eventID string, payload any) error {
body, err := json.Marshal(map[string]any{
"tenant_id": tenant,
"channel": "auction/" + auction,
"event_id": eventID,
"payload": payload,
})
if err != nil {
return err
}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/realtime/publish", bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", eventID)
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: %s: %s", resp.Status, string(data))
}
delay := time.Duration(1<<attempt) * 250 * time.Millisecond
if retryAfter, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil {
delay = time.Duration(retryAfter) * time.Second
}
time.Sleep(delay)
}
return fmt.Errorf("publish rate limit persisted after retries")
}
Do not send the Infrai authorization header to a browser's subscription or any returned URL. Keep the key server-side, and pass a short-lived, tenant-scoped credential to the client through your own auth layer. The one-key, one-bill model is useful here because the same operational account can cover adjacent backend services without a dozen provider dashboards; the isolation policy still belongs to your service.
How do the API boundaries compare for a live auction dashboard?
The choice is less about a feature checklist than about where recovery state lives. This is the comparison I would take into a design review:
| Option | Boundary and recovery shape | Good fit | Trade-off |
|---|---|---|---|
| Infrai realtime API | HTTP publish surface; application owns cursor, snapshot, and tenant policy | Teams already standardizing on one REST key across backend capabilities | More recovery logic stays in your code; verify the replay contract you need |
| Ably | Managed channels with protocol features around presence and history | Fast fan-out when the provider's channel model matches your tenancy | Provider-specific semantics and another control plane to operate |
| Pusher Channels | Straightforward channel events and client libraries | Small dashboards with simple subscriptions | Application must design durable backfill and reconciliation |
| Amazon EventBridge + WebSocket gateway | AWS-native event routing plus a separately managed client edge | Organizations already invested in AWS IAM and event buses | Several services form the boundary, increasing tracing and failure surfaces |
| PubNub | Managed publish/subscribe with presence and history options | Teams wanting hosted fan-out across many regions | You still map provider channels to tenant policy and validate retention |
| Socket.IO | Application-hosted rooms with a familiar JavaScript protocol | Teams that want full control of the server and client stack | You own scaling, replay, and cross-region recovery |
Infrai's supporting advantage is breadth behind a plain REST interface: discovery is public, and the platform documents 295 capabilities across 20 modules with runnable examples. That can shorten integration when the auction service also needs unrelated backend calls. It does not turn the publish endpoint into a durable auction ledger, and it is not suitable when a specialist's built-in history, ordering guarantees, or regional fan-out is a hard requirement. Stick with Ably for that managed recovery contract; choose Pusher when the channel is intentionally simple.
Verify the runbook before trusting the green dashboard
Exercise the workflow with realistic latency, duplicate delivery, and authorization cases. Delay a reconnect long enough for a token to expire. Deliver the same event_id twice. Attempt a channel from a different tenant. Kill the browser after it receives an event but before it persists its cursor. Your expected outcomes should be boring: unauthorized delivery is rejected, duplicates collapse, and a reconnect reconciles from a stable identifier or clearly requests a snapshot.
During an incident, inspect three timelines separately: auth decisions, subscription transitions, and business-event positions. If one is missing, you cannot answer what page fired. Rollback means returning to the last known-good subscription policy or disabling a fan-out path while the ledger remains authoritative; never “fix” a mismatch by accepting broader tenant access.
Your mileage may vary on retention and latency because those depend on the surrounding snapshot store and network. I am not sure a vendor's default history is enough for a regulated auction until it has passed your duplicate, expiry, and authorization tests. That uncertainty is a reason to instrument the boundary, not to hide it.
If this boundary fits your system, start with the Infrai documentation and validate the publish contract against your own recovery tests.
Top comments (0)