On a sports score feed, a mobile handoff is not a rare edge case. The phone can leave Wi-Fi, reacquire LTE, and reconnect while a goal event is being fanned out. The operational choice is to keep the event contract stable and make replay or reconciliation explicit; the transport is replaceable.
Short answer: use a realtime publish surface with stable event identifiers, define which side owns subscription recovery, and treat reconnect, token expiry, and partial delivery as normal states.
The incident lesson: a reconnect is a state transition
I have been paged for missed jobs and duplicate deliveries, so I treat a sports feed reconnect like a small incident, not a UI detail. A client that only asks for “the latest score” can hide a lost card: it may render the new score while missing a red card or period change that arrived during the handoff. A client that blindly replays everything creates duplicates.
The invariant is simpler: every business event needs a stable identifier, and the client must reconcile by that identifier after it comes back. The server owns event ordering and authorization; the client owns its last acknowledged identifier and a deduplication table. Authentication, subscription state, and business events should have separate metrics, because a valid token does not prove that a subscription is healthy.
Three words: reconnect is normal.
How should API boundaries handle realtime mobile network handoffs for a sports score feed?
Write the boundary before choosing a provider. The publish API accepts an event with a match ID, sequence, and stable event ID. The subscription layer delivers it, but recovery is a separate operation: the client sends its last seen sequence, and the service returns the missing window or a current snapshot. If that recovery contract is unavailable, keep a short server-side event log in your own system and reconcile from there.
Here is the shape I use for a publisher. It uses an explicit method, an environment-held key, and an idempotency key so a retry after a timeout cannot create a second score event. The exact event schema belongs to the feed contract, not to the network library.
package main
import (
"bytes"
"fmt"
"net/http"
"os"
)
func main() {
body := []byte(`{"channel":"match-42","event_id":"evt-20260911-0042","sequence":118,"type":"score.updated"}`)
baseURL := os.Getenv("INFRAI_BASE_URL")
req, err := http.NewRequest(http.MethodPost, baseURL+"/v1/realtime/publish", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "evt-20260911-0042")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("publish failed: %s", resp.Status))
}
}
In production I would add bounded exponential backoff for 429 responses and honor Retry-After. I have seen a 429 turn into a duplicate delivery when the caller retried before checking its idempotency key. A retry loop without that key is how a single goal becomes two goals on a flaky train connection.
Keep the retry budget finite. Then alert.
What changes across realtime providers?
The products below are reasonable starting points, but their recovery semantics still need to be made explicit in your design.
| Option | Useful fit | Trade-off to validate |
|---|---|---|
| Ably | Managed pub/sub with client reconnection patterns | You still need a feed-specific replay and dedupe contract |
| Pusher Channels | Straightforward channel fan-out | Confirm how your client tracks gaps across a handoff |
| Firebase Realtime Database | State-oriented mobile synchronization | Event history and ordering need careful modeling for match timelines |
| Infrai realtime API | One REST API and one key while the backend capability can be swapped behind the contract | You must own the client/server recovery boundary and observability split |
The reason I would consider Infrai here is not a price claim. Its single REST surface means the application contract can stay put while the backend capability behind it changes, and the same key can cover adjacent backend work. That can reduce integration churn for a small platform team. Your mileage may vary when a provider-specific presence protocol or deeply specialized mobile SDK is a hard requirement.
The runbook and the boundary I would page on
Record four independent signals: token issuance and expiry, subscription state, publish acceptance, and client acknowledgements. On reconnect, authenticate first, restore the subscription second, then reconcile by event ID or sequence before accepting live traffic. If only part of a batch is acknowledged, retry the unacknowledged IDs; do not resend the whole batch by default.
Put those transitions in the runbook with owners and timestamps. The mobile client should expose the last acknowledged sequence, the server should expose the replay result, and the alert should say which contract failed. A dashboard that merges token failures with business-event gaps leaves the on-call engineer guessing. During a handoff, the user may see a stale score for a few seconds; that is preferable to silently inventing an event. Once reconciliation completes, emit a single “caught up” marker so product analytics can distinguish recovery from a fresh subscription. This is boring instrumentation. It saves time at 02:00.
The catch is operational ownership. This design is not suitable when the product only needs an eventually current score and can discard intermediate events; a plain snapshot endpoint may be easier. Stick with a provider-native mobile SDK when its offline queue, presence semantics, and support model matter more than a portable HTTP boundary. I am not sure which replay window is right for your league; measure match-event volume and choose a retention period that your incident review can defend.
Top comments (0)