Fibric. Docs fibric.io →
v1.0.0 · stable
Platform

Streaming events

The Events API lets you page through the envelope log; the stream lets you follow it live. A single long-lived HTTP connection delivers every new envelope your filters match, as server-sent events, with a cursor on every frame so a dropped consumer resumes exactly where it left off. This page covers subscribing, filtering, cursors and resume tokens, delivery semantics, and working example consumers. The streaming endpoint is part of the v1.0 surface.

The stream endpoint

GET /v1/events/stream holds the connection open and writes one SSE frame per matching envelope. Authentication is the same Bearer key as every other route, and the stream is tenant-scoped by that key: you receive your tenant's envelopes and nothing else.

bash
curl -N https://api.fibric.io/v1/events/stream?event_type=order.* \
  -H "Authorization: Bearer sk_live_3f9c2a7b8e1d4f60a2c9" \
  -H "Accept: text/event-stream"

Each frame carries the envelope as JSON, the event's id as the SSE id field, and a cursor inside the data. A comment frame is written periodically as a keep-alive, so idle streams survive intermediaries that reap silent connections.

text/event-stream
id: ev_3a91c7
event: envelope
data: {"event_id":"ev_3a91c7","reseller_id":null,"tenant_id":"tn_9d40",
data:  "workspace_id":null,"source":"magento","event_type":"order.updated",
data:  "correlation_id":"co_51bb02","payload":{"order_id":"SO-10884","status":"processing"},
data:  "agent_id":null,"session_id":null,"cursor":"cur_eyJpZCI6ImV2XzNhOTFjNyJ9"}

: keep-alive

Filtering

Filters are query parameters and combine with AND, the same filter vocabulary as GET /v1/events. Filtering server-side keeps the connection proportional to what you actually consume.

ParameterTypeDescription
event_type string · query Exact type or glob, with the same semantics as operator triggers: * matches a single dot-delimited segment, so order.* matches order.created but not order.refund.issued.
source string · query Only envelopes from this source, for example magento or operator:jenny.
workspace_id string · query Only envelopes scoped to this workspace. Useful when workspaces separate environments.
entity string · query Only envelopes whose triggered plans touched this entity_key, the live counterpart of the list API's entity reconstruction.
cursor string · query Start position. Omitted, the stream starts at now; supplied, it replays forward from the cursor. See below.

Cursors and resume tokens

Every frame carries a cursor: an opaque token naming a position in your tenant's event log. Persist the cursor of the last frame you fully processed, and you hold a resume token. There are two equivalent ways to hand it back:

Cursors from the stream and next_cursor values from GET /v1/events name positions in the same log, which enables the standard backfill-then-tail pattern with no gap and no seam:

backfill, then tail
1. page GET /v1/events with your filters until has_more is false
2. keep the final next_cursor
3. open GET /v1/events/stream?cursor=<that cursor>
   → every event after your backfill, exactly in log order, then live

Cursors are opaque and query-bound: a cursor is valid for the filter set it was issued under, and presenting it with different filters fails with 400 invalid_cursor (Errors). A cursor older than the event retention window can no longer be resumed; restart with a backfill.

Delivery semantics

The stream inherits the platform's delivery model; it does not invent a stronger one.

i
Streams observe; operators act

If what you are building is "when X happens, do Y against a business system", you likely want an operator, which gets the trust gate, single-flight, idempotency, and receipts for free. Streams are for the surrounding tissue: dashboards, data pipelines, notification fan-out, and your own monitoring.

Example consumers

Node, with resume and dedup

consumer.ts
import { EventSource } from 'eventsource';

const seen = new Set<string>();          // dedup on event_id (bounded store in production)
let cursor = await loadCheckpoint();     // last durably processed cursor, or null

const url = new URL('https://api.fibric.io/v1/events/stream');
url.searchParams.set('event_type', 'order.*');
if (cursor) url.searchParams.set('cursor', cursor);

const es = new EventSource(url, {
  headers: { Authorization: `Bearer ${process.env.FIBRIC_API_KEY}` },
});

es.addEventListener('envelope', async (frame) => {
  const env = JSON.parse(frame.data);
  if (seen.has(env.event_id)) return;   // at-least-once: replays are expected
  seen.add(env.event_id);

  await process(env);                    // your handler
  await saveCheckpoint(env.cursor);      // checkpoint AFTER processing, not before
});
// EventSource reconnects automatically, sending Last-Event-ID as the resume token.

The checkpoint-after-processing order is what makes the consumer crash-safe: a crash between process and saveCheckpoint means the frame is delivered again on resume, and the event_id dedup absorbs it.

Shell, for inspection

bash
# watch one entity's events as they happen
curl -N "https://api.fibric.io/v1/events/stream?entity=order:magento:SO-10884" \
  -H "Authorization: Bearer $FIBRIC_API_KEY" \
  -H "Accept: text/event-stream"

# the receipts-side equivalent, from the CLI
fibric receipts tail

Connection behavior and limits