Encrypted message sink · Elixir / OTP
OpenStream relays messages between your applications and keeps a viewable log. Everything is sealed before it arrives — a breached stream is static without your key.
No topics, no consumer groups, no dashboards to babysit. Point two apps at one URL and watch the messages land.
Payloads are opaque bytes. Seal them with AES-256-GCM before they leave your process; OpenStream stores what it cannot read.
One BEAM process and one ETS table per stream. No disk on the hot path, reads bypass the process entirely.
The whole protocol is HTTP. Anything that can POST bytes can publish; anything that can read an event stream can tail.
$ curl -X POST http://localhost:4000/api/streams { "stream_id": "pQ3xWJd9tKvR2mA8LfYw1g", "ingest_token": "u4… (keep this)", "viewer_url": "/s/pQ3xWJd9tKvR2mA8LfYw1g" }
// node — same idea in any language with AES-GCM const key = /* 32 random bytes, shared out-of-band */; const iv = crypto.randomBytes(12); const c = crypto.createCipheriv("aes-256-gcm", key, iv); const ct = Buffer.concat([c.update(msg), c.final(), c.getAuthTag()]); await fetch(url + "/messages", { method: "POST", headers: { authorization: `Bearer ${token}` }, body: JSON.stringify({ v: 1, alg: "A256GCM", iv: iv.toString("base64"), ct: ct.toString("base64") }) });
$ curl -N http://localhost:4000/api/streams/pQ3xWJd9…/events?since=0 id: 1 data: {"seq":1,"ts":1752300000000,"payload":"eyJ2IjoxLCJhbGciOiJBMjU2R0NNIi…"} # or, in a browser — key stays in the fragment: http://localhost:4000/s/pQ3xWJd9…#k=<base64url key>
Unlisted stream ids keep casual eyes out. The key keeps everyone out — including us.
Streams live in memory as a bounded ring buffer and evaporate after a day of silence. There is no database to subpoena, no disk to image — the ciphertext itself is the only record, and it expires.