Realtime
Connected apps get live updates over one WebSocket. This page explains how that connection behaves. The exact channels, events and payloads are in the Realtime reference:
- Socket & channels: connecting, joining, errors.
- Conversation channel: messages, reactions, typing, presence, read state and calls in one conversation.
- User channel: events for one user across all their conversations.
Realtime, push or webhook?
poolse tells the outside world about the same events in three ways. Each reaches a different place.
| Realtime (WebSocket) | Push notification | Webhook | |
|---|---|---|---|
| Reaches | Your app, while it is open and connected | The user's phone, even when the app is closed | Your backend |
| Sent by | poolse, to members of the conversation | poolse, through Apple (APNs) and Google (FCM) | poolse, to your URL |
| Auth | The user's token | Your push credentials in the dashboard | Your webhook signing secret |
| Use it for | Updating the UI live | Ringing phones, chat and missed-call notifications | Your own records, search, moderation, emails |
| Missed if offline? | Yes: reload from REST on reconnect | Delivered when the phone comes back, as Apple or Google decide | No: retried for up to about 15 hours |
See Push notifications and Webhooks.
The connection
- The SDK opens one WebSocket per client and puts every channel on it. Watching ten conversations still uses one socket.
- It authenticates with the user's token, sent when the socket connects. A missing, expired, revoked or blocked user's token is refused, and the socket keeps retrying. Blocking a user, or revoking one of their tokens, closes the sockets they have open.
- The protocol is Phoenix Channels. The SDK includes the client; you don't install anything else.
- The socket opens lazily, the first time something subscribes to a
channel, for example when a
useMessageshook mounts.
There are two kinds of channel:
conversation:<id>: one per conversation the app is showing. Only members can join. A member who is removed has the channel closed at once, and the SDK does not rejoin it.user:<id>: one per signed-in user, for events that aren't tied to the conversation on screen (a new conversation, a mention, a call that stopped ringing). A user can only join their own.
Connection status
The SDK reports one of these statuses (useRealtimeStatus() in React):
| Status | Means |
|---|---|
idle | Nothing has subscribed yet, so no socket is open. |
connecting | Opening the socket. |
connected | Open and receiving events. |
reconnecting | The connection dropped or failed; the SDK is retrying. |
closed | Closed on purpose (disconnect() or pause()). |
Show a "reconnecting" banner on reconnecting. If the status stays at
connecting / reconnecting, the usual cause is a token problem; see
Troubleshooting.
Reconnects
When the connection drops, the SDK reconnects on its own, waiting a little longer each time (from 10 ms up to 5 seconds between tries), and rejoins every channel it had.
Events sent while the socket was down are not replayed. To catch up:
- From 3.1,
useMessagesanduseConversationsreload from REST when the socket reconnects. Nothing to do if you use them. - If you use
@poolse/sdkdirectly, refetch what you show when the status goes back toconnected.
Backgrounding (mobile)
Phones suspend sockets in the background. From 3.1 the SDK has
realtime.pause() and realtime.resume():
pause()closes the socket but keeps every channel handle and its listeners.resume()gets a fresh token, reopens the socket and rejoins the channels. Hooks then reload from REST.
In React Native, mount useAppStateLifecycle() once inside the provider.
It pauses when the app goes to the background and resumes when it is
active again. While paused, push notifications cover new messages and
calls.
Sending
Sends go over REST, not the socket. useMessages().send adds the message
to the list at once with a client-generated id. When the REST response or
the message:new event arrives with that id, it replaces the optimistic
copy, so the message never shows twice. If the send fails, the optimistic
message is removed and send throws, so you can show an error and let the
user retry.
Typing indicators are the only thing the client sends over the socket.
What realtime does not do
- No fallback to long polling or server-sent events. If WebSockets are blocked, REST still works but nothing updates live.
- No replay of missed events (see Reconnects).
- No "delivered" receipts. Read state is covered in Read receipts & unread.