Skip to main content

Server-Sent Events

The SSE v2 endpoint streams every case, tour, and activity event in real time over /api/v2/events/stream. All events are delivered through a single authenticated Server-Sent Events connection with tenant-aware filtering.

Endpoint snapshot​

  • GET /api/v2/events/stream
  • Requires Authorization: Bearer <JWT>
  • Response media type is text/event-stream
  • Optional query parameter: lastEventId β€” resumes the stream from a cached event ID (see replay section)
  • Status codes:
    • 200 – live SSE stream (each event: is a specific BaseEventDTO subtype)
    • 401 – unauthenticated (ErrorDTO)
    • 429 – more than the allowed number of concurrent SSE connections per user (default 5)
    • 500 – generic server failure

Making a connection​

import { fetchEventSource } from '@microsoft/fetch-event-source';

await fetchEventSource('https://api.tourfold.com/api/v2/events/stream', {
headers: { Authorization: `Bearer ${token}` },
onmessage(event) {
console.log('event received', event.event, JSON.parse(event.data));
},
});

Resuming with lastEventId​

All events include an id: header that mirrors eventId. Persist the latest one client-side and send it on reconnect to replay missed data from the in-memory history (roughly the last 10β€―000 events per node). Assuming you cached the previous ID in lastEventId:

await fetchEventSource(
`https://api.tourfold.com/api/v2/events/stream?lastEventId=${encodeURIComponent(lastEventId ?? '')}`,
{ headers: { Authorization: `Bearer ${token}` }, onmessage: handleEvent }
);

No error given when a wrong lastEventId is provided or an eventId old enough to not be present in the memory any longer.
Only the last 100 events are preserved and can be continued from, as means of dealing with short network disturbances.
This API does not guarantee that no events will be missed while the consumer is disconnected.

Event format​

Every SSE message looks like the following:

event: CASE_CREATED:V1
id: 018fb1ba-15c1-7f2a-9f2d-2f2b0b8f3b1a
data: {"kind":"CASE_CREATED:V1","eventId":"018fb1ba-15c1-7f2a-9f2d-2f2b0b8f3b1a","timestamp":"2024-03-22T10:41:09.123Z","tenantId":"...","caseId":"...","caseData":{...}}

Common envelope fields:

  • eventId / id: – UUIDv7 generated server-side for ordering.
  • timestamp – the moment the backend recorded the change (UTC).
  • tenantId – always matches the subscriber’s tenant; other tenants’ events are filtered.
  • businessId – convenience string derived from caseId, tourId, vehicleId, etc.
  • kind – discriminator and SSE event: name.

Event kinds​

Event kindWhen it fires
CASE_CREATED:V1Case persisted for the first time
CASE_UPDATED:V1Any update to an existing case
CASE_ACTION:V1 <strong style={{color:'#c62828'}}>DEPRECATEDLegacy workflow action event; use other case events instead
CASE_COMMENT_ADDED:V1New comment entered on a case
CASE_EXTERNAL_ID_UPDATED:V1External reference (e.g., ERP/CRM) changed
TOUR_CREATED:V1 / TOUR_UPDATED:V1 / TOUR_DELETED:V1Tour lifecycle events
VEHICLE_LOCATION_UPDATED:V1 <strong style={{color:'#c62828'}}>DEPRECATEDVehicle GPS location was updated
AREA_WAITING_TIME_UPDATED:V1Operational waiting time for an area changes
HEARTBEAT:V1Synthetic heartbeat emitted roughly every 30β€―s

Connection lifecycle, limits, and errors​

  • Timeouts – All SSE connections are automatically dropped by the server every 90 seconds forcing the client to re-establish the connection. This shorter timeout ensures that dead connections (e.g., those dropped by proxies or load balancers) are quickly detected and cleaned up. The client will automatically reconnect with an up-to-date JWT token.
  • Heartbeats – Sent every ~30 seconds. They let you detect stale connections without application data. Ignore them if you only care about business events. If the backend fails to send a heartbeat (indicating a dead connection), the connection is automatically cleaned up.
  • Stale connection cleanup – The server periodically checks for connections that haven't successfully sent any data (including heartbeats) and automatically cleans them up. This prevents resource leaks from network-level connection failures.
  • Rate limiting – The server enforces both throughput limits (to avoid flooding) and a per-user concurrent connection cap (default 5). Close unused browser tabs or EventSource instances to stay under the limit.
  • Replay cache – Only the most recent ~100 events are retained for lastEventId catch-up. If you reconnect after that window, perform a manual resync through the REST APIs.

Error bodies follow ErrorDTO. See the error handling guide.