Real-Time SSE Subscriptions
High-performance Server-Sent Events (SSE) subscriptions for live data synchronization without polling.
Moul provides built-in Server-Sent Events (SSE) streaming for real-time reactivity. Whenever dynamic records are created, updated, or deleted, subscribed clients receive instant notification events over persistent HTTP streams.
Subscription Endpoints
1. Single Collection Subscription
GET /api/moul/:name/subscribe
Query Parameters:
event: Comma-separated list of event actions to filter (create,update,delete, or*). Default:*.id: Optional filter for a specific record ID.ticket: Optional short-lived single-use SSE ticket obtained viaPOST /api/realtime/ticket.
Security Note: Passing credentials in URL query strings (
?token=or?admin_key=) is strictly disabled and returnsHTTP 400 Bad Requestto prevent exposure in proxy logs, browser histories, and Referer headers. Always stream via headers (Authorization: Bearer) or exchange credentials for a 30-second single-use ticket.
2. Multi-Collection Subscription
GET /api/moul/subscribe?mouls=posts,comments,notifications&ticket=tkt_...
Client Integration Examples
Browser Client: Single-Use Ticket Exchange
// 1. Request an opaque, 30-second single-use ticket
const { ticket } = await fetch('/api/realtime/ticket', {
method: 'POST',
headers: { 'Authorization': `Bearer ${userToken}` },
}).then(r => r.json());
// 2. Open standard EventSource without URL credential leakage
const sseUrl = `/api/moul/posts/subscribe?event=create,update,delete&ticket=${ticket}`;
const evtSource = new EventSource(sseUrl);
// Listen for new record insertions
evtSource.addEventListener('create', (event) => {
const data = JSON.parse(event.data);
console.log('New record created:', data.record);
});
// Listen for updates
evtSource.addEventListener('update', (event) => {
const data = JSON.parse(event.data);
console.log('Record updated:', data.record);
});
// Listen for deletions or transition-out events
evtSource.addEventListener('delete', (event) => {
const data = JSON.parse(event.data);
console.log('Record removed or visibility revoked with ID:', data.record.id);
});
// Listen for mid-stream token expiration or revocation
evtSource.addEventListener('auth_expired', (event) => {
const data = JSON.parse(event.data);
console.warn('Session expired or revoked:', data.message);
evtSource.close();
// Redirect to login or initiate JWT refresh
});
evtSource.onerror = (err) => {
console.error('SSE connection error:', err);
};React Hook Example
import { useEffect, useState } from 'react';
export function useRecordSubscription<T>(collection: string, initialData: T[]) {
const [records, setRecords] = useState<T[]>(initialData);
useEffect(() => {
const evtSource = new EventSource(`/api/moul/${collection}/subscribe`);
evtSource.addEventListener('create', (e) => {
const { record } = JSON.parse(e.data);
setRecords((prev) => [record, ...prev]);
});
evtSource.addEventListener('update', (e) => {
const { record } = JSON.parse(e.data);
setRecords((prev) => prev.map((r: any) => (r.id === record.id ? record : r)));
});
evtSource.addEventListener('delete', (e) => {
const { record } = JSON.parse(e.data);
setRecords((prev) => prev.filter((r: any) => r.id !== record.id));
});
return () => {
evtSource.close();
};
}, [collection]);
return records;
}cURL CLI Streaming (Header Auth or Ticket)
# Direct stream using standard Authorization Bearer header
curl -N -H "Authorization: Bearer <USER_JWT>" "http://localhost:8090/api/moul/posts/subscribe"
# Direct stream using Admin Key header
curl -N -H "X-Admin-Key: <ADMIN_KEY>" "http://localhost:8090/api/moul/posts/subscribe"
# Stream using short-lived single-use ticket
curl -N "http://localhost:8090/api/moul/posts/subscribe?ticket=tkt_98a7f..."{
"action": "create",
"moul": "posts",
"record": {
"id": "rec_01J6XYZ123",
"title": "Realtime Streams in Moul",
"slug": "realtime-streams-in-moul",
"published": true,
"createdAt": "2026-08-01T12:00:00Z",
"updatedAt": "2026-08-01T12:00:00Z"
}
}Encrypted Cloak Field Masking & Stream Security
When collections contain encrypted cloak fields (e.g. SSNs, API secrets, financial information, or PII), the realtime hub guarantees privacy across persistent broadcast channels:
- Automatic Masking (
••••1234): All cloak fields are decrypted in-flight and serialized using Moul's masking algorithm (••••1234or••••), guaranteeing neither raw ciphertext nor plaintext secrets are broadcast to standard clients. - Companion Hash Stripping: Companion blind-index columns (
<fieldName>Hash, e.g.ssnHash) and internal auth credentials (passwordHash,otpCode,passkeys) are stripped unconditionally before frames reach client channels. - Strict Reveal Restrictions: Non-admin clients specifying
?reveal=allor anyrevealparameter on subscription endpoints (/api/moul/:name/subscribe,/api/moul/subscribe, or/api/realtime/ticket) receiveHTTP 400 Bad Request: "The reveal parameter is not permitted on realtime broadcast streams". - Authorized Admin Reveal: Authenticated administrators (root accounts or
X-Admin-Key) may explicitly request unmasked plaintext by supplying?reveal=all(or a comma-separated field list) either when generating a ticket (POST /api/realtime/ticket?reveal=all) or directly upon connecting to the SSE stream. Admins without?revealdefault to masked values for defense-in-depth.