Skip to content

feat(go-api): GET /v1/events/stream — Server-Sent Events endpoint as WebSocket alternative #144

Description

@Depo-dev

Summary

WebSocket connections require bidirectional support, persistent TCP connections, and a specific server-side implementation. For consumers like serverless functions, CLI scripts, and integration tests that just want to react to events without maintaining a long-lived connection, SSE is a simpler and more universal alternative — it works over plain HTTP/1.1 through any proxy, requires zero special client code in Node.js, and is trivially easy to test with curl.

This is specifically called out in the Phase 3 roadmap as part of making Trident accessible to a wider range of consumers.

Context

SSE is one-way (server to client) which is exactly what event streaming needs. The Go API already has a Redis Streams consumer feeding the WebSocket hub. The SSE endpoint reuses the same Redis consumer infrastructure — instead of writing to a WebSocket frame, it writes data: {json}\n\n to the HTTP response body.

The key difference from WebSocket is that SSE does not require connection hijacking. The handler holds the response writer open, flushes after each event, and the client reads the stream. The Go standard library's http.Flusher interface handles this without any third-party dependency.

What needs to be built

A handler GET /v1/events/stream that:

  • Requires contractId query param; returns 400 if absent
  • Accepts optional topic0 filter
  • Sets response headers: Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, X-Accel-Buffering: no
  • Registers with the WebSocket hub as a subscriber for the given contractId (the hub already handles fan-out — SSE is just another consumer type)
  • On each incoming event: writes data: {json}\n\n and calls Flusher.Flush()
  • Detects client disconnect via r.Context().Done() and exits cleanly
  • Authenticated via X-API-Key header (same as all other endpoints)
  • NOT subject to the request timeout middleware — it is a long-lived connection by design

Acceptance Criteria

  • curl -H 'X-API-Key: ...' https://api.trident.build/v1/events/stream?contractId=X streams events as they arrive
  • Client disconnect stops the goroutine immediately (verified with context cancellation test)
  • Events formatted as data: {json}\n\n — no event: or id: prefix needed for Phase 1
  • 400 returned when contractId is missing
  • SSE-specific headers present on the response
  • Integration test: connect, publish 3 events to Redis, assert 3 SSE messages received in order

Files

services/api/handlers/stream.go (new)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Stellar WaveIssues in the Stellar wave program

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions