Push (Realtime)
Send realtime events to browser and mobile clients over Server-Sent Events with CastBrick Push.
Push (Realtime)
CastBrick Push is a lightweight pub/sub layer built on Server-Sent Events (SSE). Your server publishes events to a named channel; every connected client on that channel receives them instantly — no WebSocket setup, no extra broker.
Push is free — no credits are deducted.
How it works
Your server CastBrick API Clients
│ │ │
│ POST /push/tokens │ │
│ { channels: ["orders"] } │ │
│ ─────────────────────────► │ │
│ ◄───────────────────────── { token: "eyJ..." } │
│ │ │
│ (pass token to client) │ GET /push/stream │
│ ─────────────────────────────────────────────────► │
│ │ ← SSE connection open │
│ │ │
│ POST /push/publish │ │
│ { channel, event, data } │ │
│ ─────────────────────────► │ │
│ │ event: order.created │
│ │ ────────────────────► │Three steps:
- Your backend issues a short-lived token for one or more channels
- Your client opens an SSE stream using that token
- Your backend publishes events whenever something happens
API Reference
Base URL: https://api.castbrick.co
All authenticated endpoints require Authorization: Bearer <api_key>.
Issue a token
POST /push/tokensIssues a signed token that grants a client read access to the specified channels. Tokens expire after ttlSeconds (default 1 hour).
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
channels | string[] | ✅ | Channel names the token grants access to |
userId | string | ❌ | Optional — tag the token with a user ID |
ttlSeconds | int | ❌ | Token lifetime in seconds (default 3600) |
curl -X POST https://api.castbrick.co/push/tokens \
-H "Authorization: Bearer cb_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"channels": ["orders", "notifications"],
"userId": "user_123",
"ttlSeconds": 3600
}'Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": "2026-06-06T15:00:00Z",
"channels": ["orders", "notifications"]
}Subscribe (SSE stream)
GET /push/stream?token=<token>Opens a persistent Server-Sent Events stream. Pass the token issued above in the query string. This endpoint is anonymous — the token itself carries the authorization.
The server sends a connected event immediately on open, followed by any events published to the subscribed channels.
SSE event format:
event: order.created
data: {"orderId":"abc123","total":4999,"currency":"AOA"}
event: notification
data: {"type":"info","message":"Your export is ready"}A special connected event confirms the subscription:
event: connected
data: {"channels":["orders","notifications"]}JavaScript (browser):
const res = await fetch(
`https://api.castbrick.co/push/stream?token=${encodeURIComponent(token)}`
);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const messages = buffer.split('\n\n');
buffer = messages.pop() ?? '';
for (const msg of messages) {
let eventName = 'message';
let dataLine = '';
for (const line of msg.split('\n')) {
if (line.startsWith('event:')) eventName = line.slice(6).trim();
if (line.startsWith('data:')) dataLine = line.slice(5).trim();
}
if (dataLine) {
const data = JSON.parse(dataLine);
console.log(eventName, data);
}
}
}Dart / Flutter:
import 'dart:async';
import 'dart:convert';
Future<void> subscribe(String token) async {
final uri = Uri.parse(
'https://api.castbrick.co/push/stream?token=${Uri.encodeComponent(token)}',
);
final client = HttpClient();
final request = await client.getUrl(uri);
final response = await request.close();
String buffer = '';
await for (final chunk in response.transform(utf8.decoder)) {
buffer += chunk;
final messages = buffer.split('\n\n');
buffer = messages.removeLast();
for (final msg in messages) {
String eventName = 'message';
String dataLine = '';
for (final line in msg.split('\n')) {
if (line.startsWith('event:')) eventName = line.substring(6).trim();
if (line.startsWith('data:')) dataLine = line.substring(5).trim();
}
if (dataLine.isNotEmpty) {
final data = jsonDecode(dataLine);
print('${event.event}: $data');
}
}
}
}Publish an event
POST /push/publishPublishes an event to a channel. All clients currently subscribed to that channel receive it immediately. Requires authentication.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
channel | string | ✅ | Channel to publish to |
event | string | ✅ | Event name (e.g. order.created) |
data | object | ✅ | Arbitrary JSON payload |
curl -X POST https://api.castbrick.co/push/publish \
-H "Authorization: Bearer cb_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"channel": "orders",
"event": "order.created",
"data": {
"orderId": "abc123",
"total": 4999,
"currency": "AOA"
}
}'Response:
{
"messageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"delivered": 3,
"creditsUsed": 0
}delivered is the number of SSE connections that received the event at publish time.
Developer Console
Every Push page in the dashboard has a Console tab. Use it to:
- Subscribe to any channel live in the browser — no code needed
- Publish test events and see them arrive instantly
- Copy working curl / JavaScript / Dart snippets pre-filled with your API key and channel
This is the fastest way to verify your integration before writing any code.
Common patterns
Notify a specific user
Name your channels after users: user:<id>, session:<sessionId>, etc. Issue tokens with userId set — this lets you audit which sessions are active later.
# Server: issue token for this specific user's channel
POST /push/tokens
{ "channels": ["user:user_123"], "userId": "user_123" }Broadcast to all users
Publish to a shared channel (e.g. announcements). Issue tokens for announcements alongside user-specific channels so each client subscribes to both.
Order / job tracking
Create a channel per order: order:<orderId>. Issue a token when the order page loads. Publish status updates as the order moves through your pipeline.
POST /push/publish
{
"channel": "order:abc123",
"event": "status.changed",
"data": { "status": "shipped", "trackingUrl": "https://..." }
}Reconnection
If the SSE connection drops, clients should reconnect with the same token. The Last-Event-ID header is supported — the API will replay any events missed since that ID.
// Re-fetch with Last-Event-ID to catch up on missed events
const res = await fetch(url, {
headers: { 'Last-Event-ID': lastSeenId }
});Tokens are valid until expiresAt. Issue a fresh token before expiry rather than waiting for a 401.
Security
- Tokens are signed JWTs — never expose your API key to browser clients
- Tokens are scoped to specific channels — a client can only receive events for channels listed in the token
- Tokens expire; the default TTL is 1 hour
- Publishing always requires your server-side API key
✅ Correct flow:
Browser → your server (authenticated session)
→ POST /push/tokens (with your API key, server-side only)
→ token returned to browser
→ browser connects to SSE with token
❌ Wrong:
Browser uses your API key directly to call /push/tokens