Skip to main content

SSE Bind Status

Bind is asynchronous. The gateway returns 202 immediately and processes bind in the background. The frontend subscribes to a Server-Sent Events (SSE) stream to receive real-time status updates without polling.

Why SSE

  • Bind can take 1–2 minutes (Safeco RC3 API, payment processing)
  • Heroku enforces a 30-second HTTP timeout; a synchronous bind request would time out
  • SSE provides a long-lived stream that bypasses the timeout while delivering real-time status

Endpoint

GET /api/v1/ghcms/safeco/bind/status/stream?sessionId=<uuid>

Query param sessionId is required (UUID v4). Credentials sent via cookies (withCredentials: true).

Backend

apps/apis/fastlane-api-gateway/src/app/controllers/safeco.controller.tsbindStatusStream:

  • Uses BindStatusService for in-memory status per session
  • Emits status events: { status, data?, error?, timestamp }
  • Sends last known status immediately (if any)
  • Merges live status updates from BindStatusService
  • Heartbeat every 15 seconds
  • Stream ends when status is SUCCESS or FAILED

BindStatusService (apps/apis/fastlane-api-gateway/src/app/services/bind-status.service.ts):

  • RxJS Subject per sessionId
  • setStatus(sessionId, status, data?, error?) — called by bind controller
  • getStatusStream(sessionId) — returns observable of status events
  • Cleans up 5 minutes after terminal status

Event Payload

{
"status": "PENDING" | "BINDING" | "SUCCESS" | "FAILED",
"data": { "policyNumber": "..." },
"error": "Error message if FAILED",
"timestamp": "2025-03-11T12:00:00.000Z"
}

Frontend Hook

useSafecoBindSSE (apps/fastlane-portal/src/app/hooks/use-safeco-bind-sse.ts):

  • Input: sessionId, listening (true after bind POST succeeds)
  • Connection: new EventSource(url, { withCredentials: true })
  • Events: Listens for status events
  • Reconnect: Up to 3 attempts with exponential backoff (1s, 2s, 4s)
  • On SUCCESS: Sets bindData (e.g. policyNumber), closes connection
  • On FAILED: Sets bindError, closes connection

Returns: { bindStatus, bindData, bindError, isBinding, reset }

Checkout Integration

checkout.tsx:

  • useSafecoBindSSE(session?.uuid, sseListening)
  • sseListening set to true after successful bind POST
  • On bindStatus === 'SUCCESS': navigate to /carriers/safeco/auto/success
  • BindingPolicyModal shown while isBinding is true (PENDING or BINDING)

Sequence Diagram