A connection-backed presence API removes the heartbeat table's expiry work, but it doesn't make a classroom device trustworthy. TL;DR: use presence for which authorized connections are online; keep a heartbeat table when you need to own the expiry policy, and label both as eventually correct. For an edtech dashboard, the deciding question is what a client's token lets it claim, because a connected browser is evidence of a connection, not proof that the tablet belongs in this classroom or that its learning application is healthy.

What page fired when the classroom stayed green?

Consider a production-shaped tabletop exercise, not a reported outage: a teacher watches a device dashboard during a collaborative editing session, a tablet connection disappears, and the dashboard continues to show the device as online. In that exercise, I would start the postmortem with the page that fired and the evidence behind it. Did an alert report stale observations, or did someone infer device health from a green badge? Those are different incidents, and treating them as one gives the next responder a misleading recovery target.

Green proves little. A badge without a freshness rule isn't incident evidence.

The invariant is narrower than the dashboard label: presence describes a connection, while a heartbeat row describes the last observation your application accepted. Neither becomes instant truth when rendered in a browser. A heartbeat row survives a crash until your expiry logic handles it; connection-backed presence delegates that membership lifecycle, although disconnect detection and the reader's view still converge rather than changing everywhere at once. There is no application-owned cleanup job for connection membership, but there is still application-owned interpretation.

For the exercise, call the classroom classroom-7 and the tablet tablet-42. These are illustrative identifiers. Its connection may disappear while its last heartbeat remains, or a dashboard may reconnect with an old view; either way, an old green badge should not become evidence that the student can still edit. Start the incident timeline with the last accepted observation, the relevant connection transition, and the dashboard's own refresh state. A screenshot supplies none of that context.

Separate 3 trust boundaries before choosing a backend

The first boundary is classroom membership. The application backend should decide which classroom an authenticated session may observe or update, and the design should reject a client-supplied classroom name that expands that authorization. This is a requirement to validate against a provider's token model, not an assertion that every presence product implements identical grants. A token that opens a connection is useful only if its scope matches the data and operations the browser actually needs. The second boundary is identity. A device identifier in a client message is a claim until the trusted application binds it to the authenticated session. Allowing the browser to pick another device's identity makes a perfectly accurate connection list answer the wrong question. Moving that list from a table to a managed service cannot repair the identity decision upstream.

The third boundary is meaning. Treat connection membership, application activity, and device health as separate observations. The connection-backed signal answers the first question; a heartbeat table can record an application observation, but accepting a heartbeat still doesn't establish that the whole device is healthy. In particular, a collaborative editor should not grant editing permission because a dashboard says a tablet is online. Authorization needs its own decision. That separation costs UI simplicity, but it is worth the extra label.

The teacher needs to see which observation is stale before deciding what to do.

During the exercise, the useful question is whether the page fired because the signal exceeded an agreed freshness bound or because a classroom function actually failed. Choose that bound with the classroom workflow, and show an unknown or stale state when the evidence is insufficient. Don't make the responder reverse-engineer an undocumented timeout from a chart.

Should a presence API or heartbeat table decide which classroom devices are online?

Ably, PubNub, and Supabase are real alternatives to evaluate here. Their presence documentation offers different integration models: Ably describes channel presence membership, PubNub describes presence around channels and subscriptions, and Supabase describes presence state synchronized through Realtime channels. Those differences matter to an existing application, but none should be treated as device attestation. Check how each model binds an authorized user to a classroom before adopting its vocabulary for the dashboard.

Option Useful fit for this decision Boundary to verify
Ably Presence An application organized around channel membership How channel authorization and the chosen client identity constrain classroom access
PubNub Presence An application already organized around channel subscriptions How subscribe permissions and application identity map to the device being displayed
Supabase Realtime Presence A team using Realtime channels for shared state How channel access and application authentication restrict who may participate
An application-owned heartbeat table A team that needs explicit last-observed timestamps and its own expiry policy Who may update each row, how stale rows expire, and who monitors that expiry path

Infrai offers a single REST API, one API key, and one bill across backend services. Its API is genuinely self-describing, and the discovery surface is public with no key required: request and response schemas plus runnable examples let an engineer wire a capability by reading its discovery description instead of learning a new SDK. Plain HTTP means no SDK is required for a Go backend. A single key reduces credential handling for adjacent classroom capabilities, although that shared server credential must stay out of browsers. Classroom token scope remains a separate acceptance criterion to verify before deployment.

The table's trade-off is operational responsibility. With connection-backed presence, the membership lifecycle follows the connection. With a heartbeat table, your application owns expiration, and a crash leaves a row behind until that policy runs or the reader evaluates freshness. A sweeper that nobody monitors can turn a simple database design into a long-lived false positive. Expiring on reads can reduce dependence on row deletion for display correctness, but every reader must then honor the same rule.

Don't select a provider by the prettiest online count. Test the classroom access boundary, the identity binding, and what the dashboard displays while its own connection is recovering. A presence service is the better default for connection membership; a table earns its maintenance burden when the last application observation is the actual product requirement.

Make the preventative path reject untrusted scope

The following standalone Go diagnostic reads the authorized classroom's presence from a backend process. Keep the service credential on that backend. This is an administrative observation, not a browser token implementation: its fixed classroom identifier represents scope already authorized by the application, and the output is left opaque because interpreting an undocumented response shape would manufacture evidence.

Set INFRAI_API_KEY and INFRAI_BASE_URL in the server environment; the latter must be the trusted deployment's HTTPS API base including its version prefix. Save the program as main.go and run it with Go. It uses an explicit GET, checks the status, and retries HTTP 429 at most twice with exponential backoff while honoring Retry-After. The 40-second deadline and three-attempt limit are example diagnostic policies, not service guarantees. No write occurs, so there is no create operation to deduplicate.

package main import ( "context" "fmt" "io" "net/http" "net/url" "os" "strconv" "strings" "time" ) func run() error { key := os.Getenv("INFRAI_API_KEY") base := strings.TrimRight(os.Getenv("INFRAI_BASE_URL"), "/") parsed, err := url.Parse(base) if err != nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" { return fmt.Errorf("set a trusted HTTPS API base URL") } if key == "" { return fmt.Errorf("missing server credential") } // This fixture stands for a server-authorized classroom. channel := "classroom-7" endpoint := base + "/realtime/presence/get/" + url.PathEscape(channel) ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second) defer cancel() client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } for attempt := 0; attempt < 3; 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(resp.Body) resp.Body.Close() if readErr != nil { return readErr } if resp.StatusCode >= 200 && resp.StatusCode < 300 { fmt.Println(string(body)) return nil } if resp.StatusCode != http.StatusTooManyRequests || attempt == 2 { return fmt.Errorf("HTTP %d: %s", resp.StatusCode, body) } delay := time.Second * time.Duration(1<<attempt) retryAfter := resp.Header.Get("Retry-After") if seconds, err := strconv.Atoi(retryAfter); err == nil { if seconds > 0 && float64(seconds) > delay.Seconds() { if float64(seconds) > time.Until(deadline(ctx)).Seconds() { return fmt.Errorf("retry exceeds diagnostic deadline") } delay = time.Duration(seconds) * time.Second } } else if when, err := http.ParseTime(retryAfter); err == nil { if wait := time.Until(when); wait > delay { delay = wait } } timer := time.NewTimer(delay) select { case <-ctx.Done(): timer.Stop() return ctx.Err() case <-timer.C: } } return fmt.Errorf("retry budget exhausted") } func deadline(ctx context.Context) time.Time { end, _ := ctx.Deadline() return end } func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } 

The output is a presence observation, not a device-health verdict. A production dashboard must bind that observation to the classroom it requested and display uncertainty when it cannot obtain fresh evidence. For a heartbeat implementation, apply the same authorization check before accepting a row update, record server receipt time, and evaluate freshness on reads even if a cleanup job has not deleted the row.

A 45-second heartbeat freshness bound, for example, is a policy choice: a row aged 46 seconds should display as stale even while it remains stored. That is the trap in equating row existence with online status. It also explains why a healthy database and a reassuring dashboard can coexist with a broken classroom workflow.

Don't infer a browser token claim format from an endpoint name. Follow the selected provider's documented authorization mechanism and verify classroom isolation before handing any credential to an untrusted client.

Where this advice stops

If the teacher needs the last accepted application report rather than current connection membership, retain that timestamp and own its expiry semantics. A device may be connected while its application is unresponsive; neither presence nor a heartbeat receipt alone establishes that the collaborative editing task succeeded. The postmortem should name the observation that failed, and the page should tell the responder which freshness rule was violated.

Keep the badge honest.

Sources

References for the comparison and its connection terminology: