Skip to content

SSE Notification

The SSE (Server-Sent Events) notification stream is the primary mechanism for waking up a service client when new orders or commands are available. The stream sends lightweight notifications only. It never contains full order or command payloads. When a notification is received, the client should immediately poll GET /orders/location for the actual data.

Endpoint

text
GET https://{host}/notifications/service/stream

Headers:

HeaderRequiredDescription
api_keyYesService device API key

Query parameters:

ParameterTypeDefaultDescription
orderNumberlong-1Last processed order number for offset-based reconnect
commandNumberlong0Last processed command ID for offset-based reconnect

Response: text/event-stream (SSE stream using SseEmitter)

Event Flow

Once connected, the server sends events in the following sequence:

text
     Client                          Server
       |                               |
       |--- GET /notifications/service/stream --->|
       |                               |
       |<-- event: ready                 |
       |    data: {"subscriptionId": "...",|
       |           "locationName": "..."} |
       |                               |
       |<-- event: message              |  (when new orders or commands)
       |    data: {"apiKey": "...",      |
       |           "licenseCode": "...", |
       |           "scope": "..."}      |
       |                               |
       |<-- event: keepalive            |  (every 15 seconds)
       |    data: {}                   |

1. Ready Event

On successful connection, the server immediately sends a ready event with the subscription metadata:

text
event: ready
data: {"subscriptionId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","locationName":"Main Floor"}

The subscriptionId uniquely identifies this SSE connection. The locationName indicates which location the client is subscribed to.

2. Message Events

When new orders or commands are available for the location, the server sends a message event with a MessagesAvailableEvent payload:

text
event: message
data: {"apiKey":"abc123-def456","licenseCode":"LICENSE01","scope":"SESSION"}

The MessagesAvailableEvent payload:

FieldTypeDescription
apiKeystringThe API key of the subscription.
licenseCodestringThe license code for the tenant.
scopestringEither SESSION or LICENSE, indicating the broadcast scope.

This is a signal to poll. It does not contain any order or command data.

3. Keepalive Events

The server sends a keepalive event every 15 seconds to keep the connection alive:

text
event: keepalive
data: {}

If no keepalive is received for 60 seconds, the client should assume the connection is dead and reconnect.

Connection Timeout

The SSE stream has a timeout of 60 minutes of inactivity. If no data is sent for 60 minutes, the server closes the connection. The keepalive event resets this timeout, so in practice the connection stays open as long as the client receives keepalives.

Reconnection with Offset

When reconnecting after a disconnection, pass the last processed order number and command ID as query parameters. This ensures the server resumes notification from where you left off, and you do not miss any events:

bash
curl -i -H "api_key: abc123-def456-ghi789" \
  "https://{host}/notifications/service/stream?orderNumber=12345&commandNumber=42"
  • orderNumber — The highest order number you have already processed. Pass -1 or omit for no offset (start from the beginning).
  • commandNumber — The highest command ID you have already processed. Pass 0 or omit for no offset.

The server uses these offsets to filter which orders and commands trigger notifications. It tracks the pushed offsets in memory for each subscription.

Error Responses

The SSE stream may return the following HTTP status codes:

StatusMeaning
401Unauthorized — Invalid or missing API key
503Service Unavailable — Server is temporarily unavailable. Client should retry with exponential backoff

Client Implementation Guidance

Using EventSource API (Browser)

javascript
const apiKey = "abc123-def456-ghi789";
const lastOrderNumber = 12345; // your last processed order number
const lastCommandNumber = 42;  // your last processed command ID

const eventSource = new EventSource(
  `https://{host}/notifications/service/stream` +
  `?api_key=${apiKey}` +
  `&orderNumber=${lastOrderNumber}` +
  `&commandNumber=${lastCommandNumber}`
);

eventSource.addEventListener("ready", (event) => {
  const data = JSON.parse(event.data);
  console.log("Connected. Subscription:", data.subscriptionId);
  console.log("Location:", data.locationName);
});

eventSource.addEventListener("message", (event) => {
  const data = JSON.parse(event.data);
  console.log("New data available for apiKey:", data.apiKey);
  // Immediately poll GET /orders/location
  pollOrdersAndCommands();
});

eventSource.addEventListener("keepalive", () => {
  // Connection is still alive. No action needed.
});

eventSource.onerror = (err) => {
  console.error("SSE connection error, reconnecting...", err);
  // EventSource automatically reconnects, but you may want to
  // implement exponential backoff and update offset params
};

Using an SSE Client Library (Java/Kotlin)

kotlin
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.util.concurrent.atomic.AtomicLong

class SseClient(private val apiKey: String) {
    private val lastOrderNumber = AtomicLong(-1L)
    private val lastCommandNumber = AtomicLong(0L)
    private val client = HttpClient.newHttpClient()

    fun connect() {
        val url = "https://{host}/notifications/service/stream" +
            "?api_key=$apiKey" +
            "&orderNumber=${lastOrderNumber.get()}" +
            "&commandNumber=${lastCommandNumber.get()}"

        val request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .GET()
            .build()

        client.send(request, HttpResponse.BodyHandlers.ofLines()).use { response ->
            response.body().forEach { line ->
                when {
                    line.startsWith("event: ready") -> handleReady()
                    line.startsWith("event: message") -> handleMessage()
                    line.startsWith("data:") -> handleData(line.removePrefix("data:").trim())
                }
            }
        }
    }
}

Reconnection with Exponential Backoff

javascript
function connectWithBackoff(apiKey, maxRetries = 10) {
  let retryCount = 0;
  let lastOrderNumber = 0;
  let lastCommandNumber = 0;

  function connect() {
    const url = `https://{host}/notifications/service/stream` +
      `?api_key=${apiKey}` +
      `&orderNumber=${lastOrderNumber}` +
      `&commandNumber=${lastCommandNumber}`;

    const es = new EventSource(url);

    es.addEventListener("ready", (event) => {
      retryCount = 0;
      lastOrderNumber = 0;
      lastCommandNumber = 0;
    });

    es.addEventListener("message", () => {
      pollOrdersAndCommands();
    });

    es.onerror = () => {
      es.close();
      if (retryCount < maxRetries) {
        const delay = Math.min(1000 * Math.pow(2, retryCount), 60000);
        retryCount++;
        setTimeout(connect, delay);
      }
    };
  }

  connect();
}

What the Client Must Do

  1. Open the SSE connection with the API key.
  2. On ready, record the subscriptionId and locationName.
  3. On message, immediately call GET /orders/location.
  4. Keep track of the last processed orderNumber and commandNumber for reconnection.
  5. If the connection drops, reconnect with exponential backoff, passing the offset parameters.

What the SSE Stream Does NOT Do

  • The SSE stream does not contain order data or command payloads. It is a wake-up signal only.
  • The SSE stream does not acknowledge that you have processed orders. Processing acknowledgement happens through the status update and command response endpoints.

Next Steps