01 · WhyOne place instead of a dozen tools
Streamers juggle a dozen separate services. Omiros pulls the important ones into one place: a bento page they make their own identity, community events like subgoals, and bots that react to what happens on stream in real time.
It's freemium, and it's the project where I forged the backend blueprint the rest of my work now reuses.
02 · The realtime spine"Realtime" is easy to fake, so here are the actual transports
Twitch events arrive as EventSub webhooks. A Go core processes them. The pieces after that each use a different transport, chosen for its channel rather than for the buzzword.
- 01Overlays in OBS receive updates over Server-Sent Events — one-way, because an overlay never talks back. SSE gives auto-reconnect and rides plain HTTP with no upgrade handshake, so a WebSocket here would be over-engineering.
- 02The Discord bot runs on a persistent Gateway WebSocket.
- 03Lower-stakes reads simply poll. Three transports, no cargo-culting.
03 · The decision I'm proudest ofOverlays run untrusted code, so they run somewhere else
Streamers can put arbitrary animation scripts into their overlays. So overlays are served from a separate, cookieless origin and rendered in a sandboxed iframe — a malicious overlay can't reach the session on api.omiros.me because the cookies simply aren't there. Most people would serve overlays from the same domain and get burned.
The same second-order thinking shows up elsewhere: banned words on public pages are blocked at save, so a troll can't get a streamer Twitch-banned through a page the streamer didn't even write.
04 · A trade-off on purposeFull reload over clever patching
When an overlay updates, the page reloads in full rather than patching the DOM. It's a blunt instrument — it flashes, and it kills smooth animation — and I chose it knowing that.
Overlays update rarely (a sub, a follow, a milestone), run unattended for hours, and a frozen overlay mid-stream is a public incident. A full reload means the overlay is consistent with the backend by construction, with almost no client state to drift. Robustness over elegance, with the limit named out loud.
05 · TimelineThe expensive work was invisible — and I killed a finished feature
Most of the four and a half months went into things you can't see on screen: multi-tenant overlay security, the Twitch EventSub lifecycle, a billing rebuild.
I also deleted a finished feature. Subathon shipped as a complete, tested module — then I removed it wholesale. Not because the code was bad, but because I couldn't answer how the user should experience it, and building backend behind an undecided UX is building waste. I'd rather leave an honest gap than a bad foundation something else comes to depend on.