Building a Realtime Payment Dashboard with WebSockets
Architecting a sub-second transaction feed for merchants — handling token auth, connection lifecycle, mobile backgrounding, and debounced state sync across 155+ components.
When a customer taps their phone at a store in Japan, the merchant needs to see that transaction appear on their dashboard in under a second. Not 'eventually consistent'. Not 'refresh to see new results'. Right now, as it happens. I built that realtime feed — and the hardest part wasn't the WebSocket connection itself. It was everything around it.
The Real Challenge
The WebSocket implementation wasn't technically complex. What made it hard was the environment: mobile browsers with flaky networks, app backgrounding, auth token expiration, and 10+ components all needing the same stream of transaction data without 10 separate connections.
What we had to solve:
- Token acquisition — scoped JWT per merchant session, refreshed before expiry
- Singleton connection — one WebSocket shared across all dashboard components
- Mobile lifecycle — close on background to save battery, reconnect with fresh token on foreground
- Graceful degradation — auth failures (no retry), network transitions (wait and retry), server closures (immediate reconnect)
- Debounced summary refresh — transaction list updates instantly, but revenue totals need to stabilize before refresh to avoid flickering
The Singleton Pattern
The core insight: 10 components each opening their own WebSocket would waste resources and create inconsistent states. A singleton client with a Vue mixin lifecycle provided a single connection with declarative consumption — connect, reconnect, auth refresh, cleanup — all in one place.
Custom close codes distinguished failure modes: 3001 for auth failure (never retry), 3002 for app backgrounding (stop gracefully), 3003 for network errors (retry with exponential backoff). This classification was critical for reliable operation on mobile devices.
Lessons Learned
- Realtime UX must account for mobile lifecycle, network recovery, auth refresh, and backpressure from day one. These are not edge cases — they're the normal operating environment.
- Debouncing state synchronization is more nuanced than it seems. Transaction lists can update instantly, but summary totals need to stabilize before refresh to prevent UI flickering.
- Production observability is non-negotiable for realtime systems. Every WebSocket event, reconnection, and error was instrumented through New Relic and Sentry.
The WebSocket approach eliminated 40+ unnecessary API calls per merchant per day and made the dashboard feel instant. But the real work was designing for the unreliable — mobile networks, app lifecycle, and auth expiration.