Real-time & WebSockets
Request/response is a letter; realtime is a phone call. WebSockets give your FastAPI apps a live wire — streaming LLM tokens, chat, dashboards — and webhooks give the outside world a way to call YOU.
▶ Watch this reelWhat you'll learn
- WebSockets: the live wire
- Building realtime apps
- Webhooks: inbound events
- Production hardening
Remember this
- WebSocket upgrades HTTP into a full-duplex frame pipe — either side pushes anytime; delivery isn't guaranteed, so acks are your protocol's job
- Connection manager (rooms/broadcast) + token streaming are the two patterns — async lets one thread interleave thousands of live connections
- Webhooks invert HTTP: verify HMAC signatures on raw bodies, dedupe by event ID, ACK in milliseconds and process via queue
- Production needs heartbeats (zombie detection), bounded queues (backpressure), and a pub/sub backbone (Redis) to scale past one replica
WebSockets
- Upgrade handshake (101); full-duplex frames; delivery NOT guaranteed — acks live in your protocol.
- FastAPI: @app.websocket; accept/receive_text/send_text; WebSocketDisconnect is normal exit.
- Scale unit: connections, not requests.
Patterns
- ConnectionManager: rooms → set of sockets; broadcast loops; prune on send failure.
- Token streaming: LLM async stream → socket; '[DONE]' sentinel; never block the loop.
Webhooks
- Verify HMAC over raw body, compare_digest; reject bad signatures.
- Idempotent by event ID (providers redeliver); fast ACK then queue; watch out-of-order.
- Webhook ingests the world; WebSocket fans out to clients — complements.
Production
- Heartbeat ~30s; two missed → close.
- Bounded outbound queues; drop-oldest for live data.
- Multi-replica: local sockets + Redis pub/sub fanout (PY-21).
Cross-links
GA-07 (streaming latency), AG-19 (async jobs, idempotency — the same trio), AG-23 (secrets/trust), GA-28 (realtime voice rides these wires), PY-21 (Redis for the backbone and dedupe store).
Code: Realtime, end to end
# The live pipeline:
# browser ↔ WebSocket ↔ FastAPI ↔ LLM stream
# world → webhook → FastAPI → queue → broadcast
@app.websocket("/live/{room}")
async def live(ws: WebSocket, room: str):
await manager.join(room, ws)
beat = asyncio.create_task(heartbeat(ws))
try:
async for token in llm.stream(await ws.receive_text()):
await ws.send_text(token) # tokens as they're born
await ws.send_text("[DONE]")
except WebSocketDisconnect:
pass
finally:
beat.cancel()
# Heartbeat in the background, tokens in the foreground,
# disconnects handled in the finally — a socket that
# survives production is a socket with all three.