Realtime Event Pipeline: Bridging Webhooks to Reactive UI
The Problem
In modern web applications, critical business state frequently mutates out-of-band on the server. For example, when an external Stripe webhook arrives confirming an invoice payment, or when a partner shares an updated compliance document, the change happens asynchronously in a background worker or webhook handler.
Forcing users to manually refresh their browser or running aggressive background polling intervals creates a sluggish user experience and wastes server resources. The application needed a lightweight, scalable mechanism to reflect backend state changes across active browser sessions instantaneously.
What I Built
I engineered an event-driven pub/sub synchronization bridge connecting backend webhook handlers to frontend React Query state:
- Transactional Server Publishing: Configured backend controllers to publish lightweight event signals to Ably pub/sub channels strictly after database transaction commits succeed.
- React Realtime Bridge: Built a custom React context provider (
RealtimeProvider) and lifecycle hook (useSubscriptionChannel) that manages persistent WebSocket connections per authenticated company session. - Targeted Cache Invalidation: Connected incoming channel events to TanStack React Query key invalidators, prompting the client to re-fetch canonical data without passing bloated or unverified payloads across the socket.
- Document Sharing Integration: Reused the channel topology to deliver live document list refreshes when collaborative files are shared between organization relationships.
The Interesting Engineering
1. Invalidation Signals vs. Full State Broadcasting
A common pitfall in realtime architectures is attempting to broadcast complete entity trees over WebSockets. This introduces severe synchronization bugs: out-of-order socket packets can overwrite newer local data with stale snapshots, and sensitive fields risk leaking across channel boundaries.
Instead, I implemented an invalidation-first pattern. The pub/sub message carries only minimal metadata (e.g., event type and company identifier). Upon receipt, the React client marks the corresponding query key (e.g., ['subscription', 'company', companyId]) as stale, triggering an authenticated REST fetch that passes through standard permissions and relational validations.
2. Transaction Ordering & Consistency
Realtime notifications must never fire before database mutations commit. If a WebSocket message reaches a fast client before the backend transaction completes, the client's subsequent fetch might retrieve pre-mutation state, resulting in a false "flash" of outdated data.
I structured backend handlers so pub/sub publishing occurs exclusively after database commits succeed, ensuring absolute consistency across read replicas and cache stores.
3. Connection Lifecycle Management
The client-side provider manages token rotation, graceful reconnects, and background tab transitions. When a user sleeps or switches browser tabs, the client cleanly pauses and re-synchronizes upon focus without flooding the backend with duplicate connection handshakes.
Result: Shipped and running in production with real clients, providing seamless UI reactivity for subscription changes and shared document workflows with zero continuous polling overhead.