Zstem

Live Streaming Platform

by Zstem · published 2026-07-16 · 0 forks

APIDatabaseArchitectureDocsSequencefull-stackevent-drivenserverless

Video live-streaming reference architecture: broadcaster ingest via IVS, transcoding, CloudFront delivery to viewers, WebSocket chat backed by DynamoDB, and VOD

Open & fork this on Zstem →
Broadcaster (OBS)Amazon IVS IngestIVS Transcoder (ABR)CloudFront CDNVOD Recordings BucketVOD PublisherEventBridgeStream Events HandlerViewerWeb App (player + chat)WebSocket API (Chat)Chat HandlerChat TablesREST APIStreams ServiceStreams & Users TablesCognito User PoolLive Streaming Platformzstem.design

Architecture

Sequence

Participants: Broadcaster (OBS), REST API / Streams Service, Amazon IVS, EventBridge, Stream Events Handler, DynamoDB, CloudFront CDN, Viewer, WebSocket API, Chat Handler

API contract

Database

users
user_idCognito sub
username
email
display_name
avatar_url
is_broadcaster
created_at
streams
stream_id
broadcaster_id
title
categoryGSI with status for browse-by-category
statusoffline | live | ended — GSI partition key for the live directory
ingest_endpointRTMPS endpoint handed to OBS
stream_key_secret_arnstream key lives in Secrets Manager, never in this table
playback_urlCloudFront HLS master playlist
started_at
ended_at
peak_viewers
created_at
chat_messages
message_id
stream_idpartition key in DynamoDB; sent_at is the sort key
user_id
usernamedenormalized for render without a user lookup
bodymax 500 chars, moderated client + server side
sent_at
chat_connections
connection_idAPI Gateway WebSocket connection id
stream_idGSI for fan-out: all connections watching a stream
user_idnull for anonymous viewers (read-only chat)
connected_at
ttlepoch seconds; reaps zombie connections
vods
vod_id
stream_id
broadcaster_idGSI for a channel page VOD list
title
s3_keymaster playlist key in the recordings bucket
thumbnail_url
duration_seconds
published
created_at
follows
follow_id
follower_id
broadcaster_idGSI for follower counts and go-live notifications
created_at

About this design

What this is

A reference architecture for a Twitch-style live streaming platform on AWS. Broadcasters push RTMPS into Amazon IVS (modeled as custom nodes — ingest and ABR transcoder), viewers pull HLS through CloudFront, chat rides an API Gateway WebSocket backed by Lambda and DynamoDB, and every finished stream is auto-recorded to S3 and published as a VOD. A small REST API (streams, VODs, users) and Cognito round out the control plane. The sequence diagram walks the two paths that matter most: a broadcaster going live and a viewer joining to watch and chat.

How it works

Go live. A broadcaster calls POST /streams, which provisions an IVS channel and returns the RTMPS ingest endpoint plus a stream key (the key itself lives in Secrets Manager — only its ARN is stored). When OBS starts pushing, IVS emits a Stream State Change: START event onto EventBridge; the stream-events handler flips the stream to live and stamps the CloudFront playback URL. Viewers discover it via GET /streams?status=live, which reads a GSI on stream status.

Watch. Playback is plain HLS: the player fetches the master playlist from CloudFront and adapts across the transcoder's bitrate ladder. Video never touches our Lambdas — the data plane is entirely IVS + CloudFront, so viewer count doesn't affect API load.

Chat. The web app opens a WebSocket ($connect stores the connection id keyed by stream). sendMessage persists to chat_messages and fans out with postToConnection to every connection watching that stream. Connections carry a TTL so zombies get reaped.

VOD. On Stream State Change: END, IVS finalizes the auto-record to S3; the S3 event triggers the VOD publisher, which writes duration, playlist key, and thumbnail into the vods table.

How to extend

Go-live notifications — the follows table already has the GSI you need; add an EventBridge target on START that pushes via SNS/Pinpoint.

Chat moderation — insert a moderation Lambda (banned-word list, or Comprehend toxicity scoring) between the sendMessage route and persistence.

Viewer counts — aggregate chat_connections per stream on a 10s schedule and expose it on the stream record.

Clips — a MediaConvert job that cuts a time range from the S3 recording; add a clips table.

Low-latency mode — swap the HLS path notes for IVS low-latency playback (~2s) and note the CDN implications.

Chat fan-out is the first thing to shard when a single stream gets big — batch postToConnection calls and consider moving hot streams to a dedicated fan-out worker fed by a Kinesis stream.

Open & fork this on Zstem →