A plain-English walkthrough of the encryption and infrastructure controls in the running code. Every claim below describes what the deployed code does today — not what is planned, and not what the underlying library is capable of. Where a protection is weaker than you might assume, it is written down in Known limitations rather than left out.
Every message is encrypted in your browser and stays encrypted for its entire journey. The key is derived from the secret half of your room key — a value that is never sent to us — so the encryption happens entirely on your device, and ours only ever handles the result.
Today that is a single AES-256-GCM key per room, held for as long as that room's key generation lasts, with a fresh random initialisation vector for every message. It is not a new key per message, and it does not give you forward secrecy: someone who obtains a room key can read every message sent in that room under that key. Rotating the key generation — which happens when you revoke a guest — is what puts a hard boundary between an old key and everything after it.
Encrypted messages travel over a WebSocket connection to our server, which stores the encrypted blob and forwards it to the other participant. We hold ciphertext and never the text itself — but we do handle every message, and we can see the room identifier, message sizes, and when messages are sent.
The app also opens a WebRTC connection for voice and video. That connection is deliberately forced through a relay rather than joining the two browsers directly, so neither participant learns the other's IP address. It also means the relay provider carries that traffic. A direct browser-to-browser path for text exists in the codebase but is not active in the deployed app — see Known limitations.
A room key is two independent halves. The first half is what the server checks you in with, and it only ever stores a one-way hash of it, compared in constant time so it can't leak a partial match through timing. The second half is what every encryption key in your conversation is derived from, and once your key is in your hands, that half is never transmitted to us again.
There is one exception, and it is worth stating plainly. Your Owner Key is generated on our server at the moment of purchase and held in encrypted form so that we can hand it to you. That copy is deleted the instant you collect it. If you never collect it, it stops working exactly 24 hours after your payment completes, and the hourly sweep that erases it puts the outside limit at 25 hours. For at most those 25 hours — and only then — we hold your complete key in a form we could decrypt. After that, we cannot: what remains is a one-way hash of the check-in half, and nothing that can reconstruct the secret half.
A Guest Key carries the room's shared secret inside it, which is how two people reach the same encryption key without that secret passing through us. A room has one guest at a time. Revoking that guest rotates the room's secret outright, so a key you have withdrawn cannot decrypt anything that comes after it, even in the hands of someone who kept a copy of the string.
Message bodies are stored only as the encrypted blob your browser produced, opaque to us either way. Stored messages expire automatically after 7 days, are capped at 5,000 per room, and a server-side alarm removes them on that schedule whether or not anyone reopens the room. Revoking a guest deletes them immediately, as part of the same operation.
Alongside the ciphertext we hold operational data that is not encrypted to you: the room identifier, connection slots, push notification subscriptions, and a hash used to admit connections to the room. This is what lets the service route messages at all, and it is described in the privacy policy.
A room ends in one of three ways: you give it a lifetime and that deadline arrives, you destroy it, or you revoke your guest. Any of the three deletes the conversation from our servers. A lifetime can be set when you open a room and extended later in 5/15/30-minute or 1-hour steps, up to a 24-hour ceiling, and that deadline is a real backend alarm — not a timer that stops counting when a tab closes. When it fires, every connection is force-closed and the room's access hash, keys and stored messages are wiped in a single delete.
A lifetime is optional. If you choose no expiry, the room stays available until you destroy it or revoke your guest. Stored messages in a never-expiring room are still swept after 7 days by a separate backstop, so an abandoned room does not keep its contents indefinitely.
Ending a room also tells the other participant's browser to erase its saved copy, and it does — provided their device is reachable at the time. See Known limitations.
There's no sign-up, no username, no password, and we never ask for your email address. Access to a room is the key itself: you hold it, we hold a one-way hash of half of it. The only "identity" involved in a session is a random token your browser invents for itself, and it disappears along with everything else when the room ends. There's no user table anywhere to leak.
Payment is handled by Stripe, which keeps its own record of the transaction under its own terms. We keep a record of the payment itself — amount, currency, status and Stripe's reference — because we're required to, and nothing that identifies you personally.
If you turn on notifications, what travels is a bare category label — "new message," "reaction," and so on — plus the room identifier. Never the message itself. The text you see in the notification is a fixed phrase your own device fills in locally.
Push notifications are delivered by your browser vendor's push service — Google, Apple or Mozilla, depending on the browser. The payload is encrypted to your device, so that service cannot read even the category label or the room identifier. It can see that a notification was sent to your subscription, and when.
Voice and video use short-lived TURN relay credentials generated fresh for each session — not a fixed secret shipped in the app. There's nothing long-lived to extract from the client even if someone tried.
There is no forward secrecy today. The app ships with webcrypto-ratchet, an open-source package implementing PQXDH key agreement and a Triple Ratchet, which would give a new key for every message. That layer is not running in the deployed app: the handshake it depends on cannot complete, so every message falls back to the single-room-key scheme described under Encryption. Until this is fixed, obtaining a room key retroactively exposes every message sent in that room under that key, including up to 7 days of stored messages. This page will be updated when it changes, and not before.
Text messages do not travel directly between browsers. A peer-to-peer path exists in the code but depends on the same handshake, so in practice every message goes through our relay as an encrypted blob. Voice and video do use WebRTC, deliberately relayed rather than direct so that neither participant learns the other's IP address.
The cryptography is a custom implementation, not an established library. It's built on standard WebCrypto primitives and factored out into its own open-source package (webcrypto-ratchet) so it can be reviewed and reused outside this app — but it is not the audited libsignal library, and it has not had an independent cryptographic audit. Publishing the source isn't the same thing as auditing it.
Clearing the other person's device depends on their device being reachable. Destroying a room, revoking a guest, or a room reaching its expiry all instruct the other participant's browser to erase its saved copy of the conversation, and it does. But that only happens if their browser is running and connected at the time, or the next time it reconnects. Someone who closes the tab and never returns keeps whatever they had already received. We delete our copy regardless; we cannot reach a device that isn't listening.
We hold your Owner Key briefly at purchase. The key is generated on our server and held encrypted until you collect it, or for at most 25 hours if you don't — it stops working at 24, and the hourly sweep clears it within the hour after. See Room access above. Once collected or erased, we cannot reconstruct it.
Your saved message history is encrypted, but with a key derived from your room key. Anyone holding your Owner or Guest Key and a copy of your browser's local storage can read your saved history. The encryption protects the data at rest; it does not bind it to a particular device.
We can see that a conversation is happening. Message contents are opaque to us, but room identifiers, message sizes, connection times and message timing are visible to our infrastructure and to Cloudflare, which operates it.