CastBricks Docs

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:

  1. Your backend issues a short-lived token for one or more channels
  2. Your client opens an SSE stream using that token
  3. 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/tokens

Issues a signed token that grants a client read access to the specified channels. Tokens expire after ttlSeconds (default 1 hour).

Request body:

FieldTypeRequiredDescription
channelsstring[]Channel names the token grants access to
userIdstringOptional — tag the token with a user ID
ttlSecondsintToken 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/publish

Publishes an event to a channel. All clients currently subscribed to that channel receive it immediately. Requires authentication.

Request body:

FieldTypeRequiredDescription
channelstringChannel to publish to
eventstringEvent name (e.g. order.created)
dataobjectArbitrary 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