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/subscribeAuthentication
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:paidUse 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:
| Field | Type | Description |
|---|---|---|
event | string | Event key (e.g. invoice:created) |
data | object | Event-specific payload |
timestamp | number | Unix timestamp (ms) when the event was emitted |
traceId | string | OpenTelemetry trace ID of the Tillor action that caused this event (same as with webhooks) |
spanId | string (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.