Events (SSE)¶
Server-Sent Events stream for real-time updates to session lists and in-call reactions.
Base path: /api/events
Authentication: Required.
How It Works¶
- Backend — Hono
streamSSEopens a persistent HTTP connection per client. A dedicated Redis subscriber (viaredis.duplicate()) listens on two channels per mentorship:mentorship:{id}:sessionsandmentorship:{id}:reactions. - Session mutations — Every
POST,PATCH, andDELETEon/api/sessionspublishes asession-updateevent to the sessions channel. - Call reactions —
POST /api/sessions/:id/reactionsvalidates the emoji and publishes acall-reactionevent to the reactions channel. - Frontend — The
useMentorshipSSE(mentorshipId)hook opens the stream. Onsession-updateit invalidates thequeryKeys.mentorships.sessions(mentorshipId)cache; oncall-reactionit forwards the payload to call UI callbacks for floating reaction rendering. - Heartbeat — A
pingevent is emitted every 25 seconds to prevent proxy idle-connection timeouts.
Endpoint¶
GET /api/events/mentorships/:mentorshipId/events¶
Opens a persistent SSE stream for a specific mentorship. The connection stays open until the client disconnects.
Access rules:
PlatformAdmincan connect to any mentorship's stream.- All other users must be the coach or teacher of the mentorship.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
mentorshipId |
string | Mentorship UUID |
Response headers:
X-Accel-Buffering
The X-Accel-Buffering: no header prevents Nginx from buffering the stream. Required for Dokku / reverse-proxy deployments.
Event Types¶
session-update¶
Emitted whenever a session in this mentorship is created, updated, or cancelled.
| Data field | Type | Description |
|---|---|---|
action |
string | Always "updated" |
mentorshipId |
string | UUID of the affected mentorship |
On receipt, the frontend invalidates the session list cache to trigger a background refetch.
call-reaction¶
Emitted when a participant sends an emoji reaction during a live video call.
| Data field | Type | Description |
|---|---|---|
emoji |
string | The emoji sent (one of 👍, ❤️, 😂, 🎉, 👏, 🙌) |
userId |
string | CUID of the user who sent the reaction |
sessionId |
string | UUID of the session where the reaction was sent |
ping¶
Heartbeat event sent every 25 seconds to keep the connection alive.
No action is required on receipt.
chat-message¶
Emitted when a participant sends a new chat message in the mentorship.
event: chat-message
data: {"id":"<uuid>","mentorshipId":"<uuid>","senderId":"<cuid>","body":"Hello!","createdAt":"...","sender":{...}}
| Data field | Type | Description |
|---|---|---|
id |
string (UUID) | Message ID |
mentorshipId |
string (UUID) | Mentorship the message belongs to |
senderId |
string | CUID of the sender |
body |
string | Message text |
createdAt |
string | ISO 8601 timestamp |
sender |
object | Sender profile (firstName, lastName, email) |
On reconnect, clients should call GET /api/mentorships/:id/chat/messages to fetch missed messages.
Example: Connecting from JavaScript¶
const source = new EventSource(`${API_BASE}/api/events/mentorships/${mentorshipId}/events`, {
headers: {
'X-Client-Key': CLIENT_KEY,
'X-Organization-Id': orgId,
},
withCredentials: true,
});
source.addEventListener('session-update', (event) => {
const payload = JSON.parse(event.data);
// Invalidate session list cache
queryClient.invalidateQueries({ queryKey: queryKeys.mentorships.sessions(payload.mentorshipId) });
});
source.addEventListener('call-reaction', (event) => {
const { emoji, userId, sessionId } = JSON.parse(event.data);
// Show floating reaction in call UI
showReaction(emoji, userId);
});
source.addEventListener('ping', () => {
// No-op — heartbeat only
});
React Native
On native (iOS/Android), use react-native-sse instead of the browser EventSource API. It supports custom headers on all platforms, which the browser EventSource does not.
Error responses — mentorship stream¶
| Code | Condition |
|---|---|
403 |
Caller is not a participant of this mentorship (and not a PlatformAdmin) |
404 |
Mentorship not found |
Global User Stream¶
GET /api/events/users/me/events¶
Opens a persistent SSE stream scoped to the authenticated user. Receives real-time events across all of the user's mentorships — used to update the unread message badge in the navigation tab bar without requiring a specific chat or mentorship screen to be open.
Access rules: Any authenticated user.
Response headers:
Global event types¶
chat-message¶
Emitted when a new chat message is sent in any mentorship the user belongs to.
event: chat-message
data: {"id":"<uuid>","mentorshipId":"<uuid>","senderId":"<cuid>","body":"Hello!","createdAt":"...","sender":{...}}
Same payload shape as the mentorship-scoped chat-message event. On receipt, the frontend invalidates the chat.conversations cache to refresh unread counts across the app.
ping¶
Heartbeat event sent every 25 seconds. No action required.
Error responses — global stream¶
| Code | Condition |
|---|---|
401 |
Not authenticated |