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
Files
services/api/handlers/stream.go (new)
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\nto 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.Flusherinterface handles this without any third-party dependency.What needs to be built
A handler
GET /v1/events/streamthat:contractIdquery param; returns 400 if absenttopic0filterContent-Type: text/event-stream,Cache-Control: no-cache,Connection: keep-alive,X-Accel-Buffering: nodata: {json}\n\nand callsFlusher.Flush()r.Context().Done()and exits cleanlyX-API-Keyheader (same as all other endpoints)Acceptance Criteria
curl -H 'X-API-Key: ...' https://api.trident.build/v1/events/stream?contractId=Xstreams events as they arrivedata: {json}\n\n— noevent:orid:prefix needed for Phase 1contractIdis missingFiles
services/api/handlers/stream.go(new)