Skip to Content

Server-Sent Events (SSE)

Stream realtime events via a long-running connection

Last updated on

SSE offers a long-running stream of the same events that webhooks receive. Use SSE when you prefer a persistent connection over HTTP callbacks.

Endpoint

GET /api/orgs/:orgId/realtime/subscribe

Authentication

SSE uses the same authentication as other API endpoints:

  • x-api-key - Your API key

The organisation ID is in the URL path.

Event filtering

Use the events query parameter to filter by event type:

GET /api/orgs/:orgId/realtime/subscribe?events=invoice:created&events=invoice:paid

Use events=* to receive all events (same as omitting the parameter).

See the Event index for all events, the permission per event and an example payload.

A few events have their own channel (controller logs and controller I/O). You only receive those with ?channel=controller-logs or ?channel=controller-io; the Event index states the channel.

Tillor only delivers events for which the signed-in organisation member has permission. Without bank-transactions:read (or sync), for example, you do not get bankAccountTransaction:* events, not even if you send no events filter.

Linked data from another domain follows the same rules as the API: without customers:read, a reservation:updated arrives with customer: null, and without payments:read an invoice:paid arrives with payment: null. A list then becomes [].


Event format

The payload has the same structure as webhook deliveries:

FieldTypeDescription
eventstringEvent key (e.g. invoice:created)
dataobjectEvent-specific payload
timestampnumberUnix timestamp (ms) when the event was emitted
traceIdstringOpenTelemetry trace ID of the Tillor action that caused this event (same as with webhooks)
spanIdstring (optional)Span within that trace

In the SSE stream this JSON object is on the data: line; the event key is also on the event: line. See SSE examples for a wire example.


Example: Node.js

const orgId = "org_abc123";
const apiKey = "tkn_xxx";

const url = `https://app.tillor.eu/api/orgs/${orgId}/realtime/subscribe?events=invoice:created&events=customer:updated`;

const response = await fetch(url, {
  headers: {
    "x-api-key": apiKey,
    "Accept": "text/event-stream",
  },
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const chunk = decoder.decode(value, { stream: true });
  for (const line of chunk.split("\n")) {
    if (line.startsWith("data: ")) {
      const payload = JSON.parse(line.slice(6));
      console.log(payload.event, payload.data);
    }
  }
}

Example: cURL

curl -N -H "x-api-key: tkn_xxx" \
     -H "Accept: text/event-stream" \
     "https://app.tillor.eu/api/orgs/org_abc123/realtime/subscribe"

Reconnecting

If the connection drops, reconnect with the same URL and headers. The stream does not support resumption; you only receive new events after reconnecting.


Related

Was this page helpful?

On this page