# SyncKit SSE Push Notifications SyncKit provides a Server-Sent Events (SSE) endpoint that notifies connected clients in real time when new data is available. Instead of polling, your app can subscribe to a persistent connection and pull only when something changes. ## Endpoint ``` GET /api/sync/subscribe?app_id={app_id} Authorization: Bearer ``` Requires a valid SyncKit JWT token. The `app_id` query parameter specifies which sync app to subscribe to. ## Event Format When another device pushes changes, all other connected devices for the same app and user receive a `changed` event: ``` event: changed data: ``` The event carries no payload (data is always E2E encrypted and never sent over SSE). On receiving `changed`, initiate a pull to fetch actual changes. ## Keepalive The server sends a comment line every 30 seconds to keep the connection alive and prevent proxy timeouts: ``` : keepalive ``` This is a standard SSE comment and is ignored by conforming clients. ## Client Integration ### Basic Flow 1. Authenticate and obtain a SyncKit JWT token 2. Open an SSE connection to `/api/sync/subscribe?app_id={app_id}` 3. On receiving a `changed` event, call the pull endpoint to fetch new changes 4. Reconnect on connection drop (most SSE libraries handle this automatically) ### With the Rust SDK The `synckit-client` crate provides `SyncNotifyStream` for SSE integration. `SyncKitClient::subscribe` opens the connection; `next_change()` yields `Some(())` for each `changed` event and `None` when the stream ends: ```rust let mut stream = client.subscribe().await?; // In your sync loop: while stream.next_change().await.is_some() { // The server signaled new changes, pull them. let (changes, new_cursor, _has_more) = client.pull(device_id, cursor).await?; cursor = new_cursor; } // next_change() returned None: the stream ended. Reconnect (typically after a // short delay), refreshing the token first if it has expired. ``` Keepalive comments are parsed and ignored for you. The stream does **not** auto-reconnect: when `next_change()` returns `None`, the caller reconnects. ### Selective Sync Combine SSE notifications with `PullFilter` to pull only specific tables or time ranges: ```rust let filter = PullFilter { tables: Some(vec!["tasks".into()]), since: Some(last_sync_time), ..Default::default() }; let (changes, new_cursor, has_more) = client.pull_filtered(device_id, cursor, filter).await?; ``` This reduces bandwidth and processing when your app only needs a subset of changes. ## Design Notes - One SSE connection per (app, user) pair - The broadcast channel buffers up to 16 messages. If a client falls behind, missed events are skipped. The next pull will catch up regardless. - SSE works through standard HTTP proxies and load balancers. The 30-second keepalive stays within common proxy timeout defaults. ## See Also - [SyncKit Cloud Sync](./synckit.md): Full sync API reference - [OAuth2 PKCE](./oauth.md): Authentication for SyncKit apps