What this protects, and what it doesn't.
Written for someone whose adversary is well resourced and specifically interested in them: a journalist and their sources, someone documenting abuses, someone leaving a person who will look for them, someone whose contacts are more dangerous than their words. No jargon where a plain sentence works, and nothing left out because it makes the product look worse.
This page is the detail. The threat model is the summary, organised by who your adversary is, and it is the one to read first.
Three sentences
- We can't read your messages. They're encrypted on the device before they reach us, and the keys we store are locked with passwords we never see.
- We hold the complete social graph, in plaintext, indexed. Not "we could infer the pattern" — conversation membership is an ordinary database table and one self-join lists who talks to whom. Timestamps are kept to the second. If that is what you need hidden, Haven does not hide it, and SimpleX or Session are built to.
- By default we keep every old key, so one cracked password opens your history backwards and not just forwards. The server retains every historical conversation key, wrapped to your long-term identity key. There is now a per-conversation setting that has us delete the superseded ones — but it is off unless you turn it on, it still leaves about five weeks of history recoverable, and it does nothing if the person you are talking to leaves theirs off. Exactly what it buys is further down.
- You are trusting us to serve honest code. This is a web app, and a web app has no code signing. A coerced operator does not break cryptography; they modify one bundle for one account. No browser-delivered encrypted service has solved this, ours included.
- Paying by card names you; a voucher does not. A card statement ties a legal identity, a billing address and a bank to a Haven username, and that invoice is worth more to a well-resourced adversary than the ciphertext. The prepaid voucher path attaches no name and contacts no payment processor — its one limit is that the operator knows which account redeemed which code, so it holds only if the code changed hands somewhere we cannot see. The pricing page states both halves in full.
Where encrypted messengers actually get beaten
Four large encrypted-phone networks were dismantled in recent years — EncroChat, Sky ECC, Phantom Secure and ANOM. Between them they had tens of thousands of paying users. Not one of them was beaten by breaking the encryption. It is worth knowing why, because it is the same short list of weak points for every product in this category, including ours.
The vendor's own update channel
EncroChat kept its update-signing key on its servers. Investigators took the infrastructure and pushed a signed — and therefore trusted — malicious update to every handset, which read messages before they were encrypted. No key length would have helped.
The app you install not being the app they published
With Sky ECC, the operator's account is that counterfeit apps circulated through unauthorised resellers. Users had no way to check that what was on their phone was what the vendor shipped.
An extra recipient nobody could see
ANOM was a law-enforcement operation from day one. The encryption was fine; the client quietly added a hidden extra recipient to every conversation and filtered it out of the contact list. Perfect encryption, to one more party than the user knew about.
The operator holding the routing
Phantom Secure was a server-mediated remailer, so the operator held who-talked-to-whom for everything regardless of how strong the message encryption was.
The things we will stand behind
Each of these is a property of code that exists and runs today, not a plan. Where a protection has a boundary, the boundary is in the same paragraph as the protection.
Us reading your content
Message bodies, file contents, filenames, MIME types and the names of new groups are encrypted in the browser before they're sent — groups created before group-name encryption landed still hold their name in plaintext, because dropping the old column would have destroyed existing names. Each conversation has its own random key, encrypted individually for each participant. We store ciphertext and locked key material, and nothing on our disks that carries what you said is readable by us, by an employee, or by anyone who steals the database. A great deal of what surrounds it is — the membership graph, the timestamps, the reactions, the billing records. This sentence used to end at "nothing on our disks", which the rest of this page contradicts and an audit of the schema caught.
A demand for your messages
If we are compelled to produce what we hold, what we hold is ciphertext and wrapped keys. There is no key escrow and no operator decryption path, so "hand over the messages" has no version of itself we can comply with, however the request is worded. Note the exact scope of that claim: it covers content. "Hand over who this account talks to, and when" is a request we can satisfy completely, and that is the demand this design does not defeat.
A substituted key — ANOM's trick
Haven remembers every identity key it has seen. If a contact's key changes, you get a banner; if you had verified that contact by comparing a 60-digit safety number out of band, sending is paused on every send path until you acknowledge it — and where new key material is being created, a change on a verified key can't be clicked through at all. This is the tripwire the crypto-phone users never had.
Somebody who needs to reach you and cannot have an account
Everything else here assumes both people have signed up. A newsroom, a regulator or a helpline needs the case where one of them has not, and often cannot: an account is a durable identifier, and creating one is the first thing a source should not have to do. So an account can publish a submission address. Somebody opens it, writes, and gets back a codename — the only thing they keep, shown once, and the key to the conversation is derived from it in their browser rather than stored anywhere. There is no account, no username, and no row in this server's user table: the test that says so counts the rows before and after.
What it does not protect, said where the decision is made rather than here. The page itself says both of these above the text box, because the person who most needs them is the least likely to read a security page. This service cannot hide the network connection. Reaching the page over the ordinary internet is as visible to whoever watches that network as visiting any other site, and for a source that is usually the exposure that matters rather than the contents — use Tor, and understand this software does not provide it. And a reply is not real-time. The key is wrapped for each side by a person, never by this server, so a reply cannot arrive until somebody at the other end opens their browser.
Text only, for now. A source can send a message and cannot send a document, which for a tip line is the case that matters most. It is said on the page, in the panel that opens an inbox, and here — the three places somebody would decide to rely on it.
An account that is not the organisation it appears to be
Key transparency proves a key did not silently change. It has no opinion at all on who
an account belongs to: an account named guardian can have a spotless key
history and nothing whatever to do with the newspaper. So an account can prove it
controls a domain — a DNS record, or one file at a fixed HTTPS path — and the result goes
into a second append-only log with signed tree heads, a separate tree under a separate
key from the key log. This is the gap that matters most where the decision cannot be
undone: somebody deciding whether to send a newsroom a document has to know the inbox is
the newsroom's before sending it, and there is no checking afterwards.
The boundary, in the same card as the protection, because this one is easy to read as more than it is. We perform the lookup, and you cannot repeat it. A binding is our signed, timestamped and undeniable assertion that we checked and found the proof — not an attestation by the domain's owner, and not proof to you. What the log buys is that we cannot later deny having said it, and that an operator who invents a binding has published signed evidence of having done so. The app therefore never prints the bare word "verified": it says the account proved control of a named domain to this server, on a date. There is no DNSSEC validation, so somebody who can forge DNS answers to us can forge a binding. And a binding is permanent — it can be withdrawn, which writes a second entry saying so, and it can never be deleted, including by deleting the account. The screen that offers one says that before the first claim.
Somebody writing in your name
Every message carries a detached Ed25519 signature made in the sender's browser, over
the conversation, the author, the key epoch, the sender's idempotency key, the nonce and
the ciphertext; an edit is re-signed under a separate domain naming the message it
replaces. Your app checks each signature against a key it pinned itself rather than the
one served alongside the message, so a message re-attributed to another author, or moved
into another conversation, arrives carrying a red ⚠ BAD SIGNATURE label on
the line with its timestamp and a red outline round the bubble. It is flagged rather than
hidden: the sender name a group message carries is still drawn above it, and the badge
sits beside the message rather than replacing its author — deliberately, because status
text drawn inside the bubble is status text a forged message would control. What you are
shown is a name you are being told not to believe. That badge is on
the message row and only there: a quoted reply and the pinned bar render the same message
without a verdict on it.
Until this existed the author of a message was an integer in our database and one
UPDATE could move it. The boundary, in the same paragraph as the
protection: a signature says who, not where. Where is the separate
signature described in the next card.
Us taking a message out of the middle
Each message carries a second signature, over a payload with no ciphertext in it: the
conversation, the author, the key epoch, the sender's idempotency key, and the messages
its author had already seen, each named by a hash. Delete one from the database and every
message that named it now points at nothing, and your app puts
⚠ THIS TRANSCRIPT IS NOT COMPLETE above the thread with a button that jumps
to the gap — before this, a removed message left the surrounding ones verifying perfectly
and nothing anywhere said how many there were meant to be. The boundary, again in the
same paragraph: it covers the range you have loaded, from its oldest message onward. It
does not see a message suppressed at the very end, it cannot prove your history goes back
as far as it should, it says nothing about what a message says, and it does not
say who emptied a message that was deleted rather than removed.
All seven limits are set out below.
Not being able to check what we hold
Four surfaces whose entire value is that they are true. Settings → Your data → "What this server holds about you" is enumerated from the live database when you open it, not from a list somebody maintains — today 51 tables, 305 columns and 46 items, of which 38 we can read, 7 we hold and have no key for, and 1 is a one-way hash — and a column a migration adds that nobody has classified makes the panel report itself incomplete rather than quietly shortening the list. There is a full account export, with ciphertext carried as ciphertext and its omissions written inside the file. There is a protection status behind each conversation's own "⋯" menu, reporting "unknown" and "partial" rather than rounding either up: a fully verified page with older history unloaded reads partial, and forward secrecy switched on is still partial while the other participant's choice is undisclosed, because it is theirs and we do not report it. And a periodic check-up that never asks you to do something you have already done. The limit is the one on everything here: the code drawing those panels was served by the machine they describe.
Your address leaking to whoever you call
A WebRTC call normally hands each participant the other's real IP address during connection setup, and a public STUN server learns the address, the NAT shape and the timing of every call placed through it. Haven used to do both, against a hardcoded Google server nobody chose. Calls are now restricted to relayed connections by default, through a relay the operator configures — us, on a hosted plan — with no public fallback of any kind: an instance with no relay reports calls as unavailable, with the reason shown before a microphone is opened, rather than connecting them anyway. The boundary: relaying moves the disclosure rather than deleting it. Every packet crosses that relay, so we are placed to see both addresses, when the call started and how long it ran. What we cannot do is listen, because the media stays encrypted between the two browsers with DTLS-SRTP and a relay forwards packets it holds no key for.
What encrypting a call usually costs, and what it does not cost here. Zoom's end-to-end mode switches captions off, along with cloud recording and polls; Google Meet's client-side encryption switches off captions, noise cancellation and reactions; Teams' end-to-end calls switch off captions and recording. A Haven call keeps captions, because they are made on your device by the browser's own speech recognition, asked to run locally and refused rather than sent to a server when it cannot; it keeps the browser's noise suppression; and it keeps raised hands and reactions, which travel over the call's own encrypted channel. Captions need a browser that can recognise speech locally, and the app does not offer them where it cannot.
Stolen credentials and seized devices
Optional two-factor authentication with single-use backup codes, answerable with a security key or passkey instead of a code; account lockout after repeated wrong passwords; per-device sessions you can list, revoke, or wipe remotely; auto-lock that clears decrypted keys from memory; a panic key that wipes local data on a keystroke; and a duress password that wipes local data while looking, to whoever is standing over you, like an ordinary unlock. None of these defeat code already running on the machine, and none of them are forensic erasure of a disk.
A remote wipe reaches a device that was offline when you asked for it: for 90 days, the first time that device connects it is refused and told to erase what Haven stored on it, before it can load anything. The limit is the obvious one — a device that never connects again, or whose storage was copied before it did, is not reached, and the wipe was never more than an erase of this site's data in that browser.
Each session in that list carries a coarse label — "Firefox on Linux", from the
User-Agent — and that label is the whole mechanism by which you notice a session that
is not yours. It is also a way for us to link you: an uncommon label shared by two
accounts whose sign-ins land minutes apart says, without any decryption, that one person
probably holds both. So there is a preference that stores 'Unknown device'
instead, for people keeping two identities apart on purpose. It is off by default and the
trade is stated where you turn it on, because it is a real one: with it on, an intruder's
session in your list looks exactly like your own.
Being made to give an identity to sign up
Registration takes a username and a password. There is nowhere to put an email address
and no column to hold one — the sign-up form has no such field and
users.email was dropped from the schema, taking the addresses already in it
with it — and there is no phone number field at all. That matters more than "optional"
did: "we do not have your email address" is now a statement about the database rather
than a promise about our intentions. Signal needs a phone number and Threema Work is
bought by an organisation; Haven asks for neither, so an account can exist with nothing
about it that was already tied to your name. Keep the size of that right, though — a
choose a pseudonymous username rather than your real name, keep in mind connections connect
transiently over TLS without persistent IP logging, and remember that card payments attach a
legal billing identity (unlike our prepaid vouchers). Free and the voucher path are the two ways to hold a Haven account with
nothing linking it to your name.
Stated in full here, limits included.
Strangers flooding you, from accounts that cost nothing
An account that needs no email and no phone costs a spammer nothing, and a sign-up puzzle would not change that much — a solving service does ten thousand for about £25. An open instance can still ask the device for a few seconds of work at sign-up, which stops a careless script and tests nothing about the person, but the limit that matters sits where the harm starts: reaching people you do not already talk to. An account may reach 40 people a day it shares no conversation with, and an account less than a week old may reach 10. Each such person counts once, whether they are in a new conversation or added to a group, so one group of strangers costs as much as the same number of separate chats. Conversations with people you already talk to are never limited, and nothing new is stored to count this — it is the conversations the account created in the last day, plus a short-lived in-memory tally of the further strangers it reached, which a server restart forgets. We publish the numbers because nobody else does, and a limit you cannot know is one you cannot plan around. Messages from strangers still arrive as requests you can accept or ignore.
Message length as a side channel
Message plaintext is padded to fixed size buckets — 32 bytes through 16KB — before it is encrypted, so a stored ciphertext gives away a range rather than a length. Filenames and MIME types are encrypted, and so are the names of new groups. Raw attachment bytes are not padded and their size is stored in the clear.
Someone reading over your shoulder
The app blurs itself behind a neutral placeholder when it loses focus, so content isn't sitting in the OS app switcher. Notifications default to showing the sender only, because notification text lands in OS history and on lock screens where the app has no control.
A photo's hidden baggage
EXIF, XMP and comment data are stripped before encryption from JPEG, PNG and WebP photos, and from MP4 and MOV video — losslessly, so the picture itself is byte-for-byte what you took. Encryption hides a photo from us; it does nothing about the GPS coordinates and device serial that the recipient decrypts along with it. Any other kind of picture or video — HEIC, for one — cannot be cleaned here, and Haven now says so and asks before sending it, where it used to send it in silence.
The things we won't pretend about
None of this is hedging language for a legal page. Each item is a specific gap, and if any of them matters to you, it should change your decision.
A backdoored copy of the app, served to one person
Every page load fetches executable JavaScript from a server, with no signature and no version pinning. A compromised or hostile server can serve one targeted user a modified bundle and everyone else the clean one, and that user cannot tell. Structurally this is worse than what killed EncroChat, which at least had a signing key that was supposed to be offline.
What this comes down to is who runs the server. On a hosted plan that is us, and you are trusting us in the same structural way EncroChat's users trusted EncroChat. For most buyers that trust is reasonable and ordinary — it is the trust you already place in a hosted mail provider — but place it deliberately rather than because a marketing page implied encryption made it unnecessary. It doesn't: the encrypting is done by the code we hand you.
This is not unique to Haven, and saying so is context rather than an excuse. Proton, Tuta and every other browser-delivered encrypted service has the same structural problem — the page that does your crypto is fetched from the vendor on every load, and a vendor who serves you a different page has your plaintext. It has been argued about in public for over a decade and none of those services has solved it. A native app with a signed, pinned binary is a better position, and none of us are in it.
Things that would narrow it. Subresource integrity on the script tags is
now real for the two vendored crypto files — they carry SHA-384 hashes, and the server
checks every pin against the bytes on disk at startup and refuses to boot on a mismatch,
because a malformed integrity value is silently discarded by the browser rather than
enforced. Be clear about how much that buys against this threat: nothing. The same
origin serves the document that carries the hashes, so an operator who wants to substitute
a file edits both. It catches a careless substitution and a supply-chain one, which is
worth having and is not this. What would narrow this is a release that a third party has
rebuilt from source, confirming the bytes we serve are the bytes in the repository — with
someone actually checking, that is the closest a web app gets to the signing it lacks — and
an independent audit. The hashes for that are published (RELEASE-HASHES.txt,
with a reproduction procedure), and the app checks what it is served against a signed
release log by itself, locking before any key is unwrapped on a confirmed mismatch.
But nobody outside this project has reproduced a release, and no audit has
happened. Beyond those, what exists is weaker still
and worth naming accurately: everything we serve is
unminified, unbundled JavaScript you can read in devtools. That is a genuinely low bar to
inspection and a genuinely poor defence against a targeted attack, because nobody diffs the
bundle on every page load.
Running your own instance moves this trust rather than removing it — you would be trusting a server you operate, and being the operator is a job in itself. What that path does and does not get you is at the end of this page.
The social graph, stored in plaintext and indexed
This is the most serious limitation on this page and it deserves the specificity. Who is in a conversation is not inferred from traffic — it is written down. The participants table is ordinary, unencrypted and indexed by user, and one self-join over it produces the complete list of which accounts talk to which. Anybody with the database has your network: not the words, the people.
Alongside it: messages carry timestamps at one-second resolution to preserve chronological thread ordering in your client (without the server ever knowing what the message said). Message plaintext is padded to fixed buckets before encryption, so message length genuinely leaks only a range.
Connections carry IP addresses, as every network connection does. None of them is
stored. No column in this schema holds an address — not on a session, not on a
sign-in event, not on the public contact form — and that is enforced by a test that scans
every column of every table and fails the build if one appears. Reaching an instance over
a Tor onion service is the only thing that hides client IPs from the server, and Haven
serves an Onion-Location header so an onion deployment advertises itself.
Push notifications hand a third party your timing
A push notification's payload carries no content and no sender name — deliberately, and that part is real. What it cannot avoid is the delivery itself: the message travels through a browser vendor's push service, which sees your device endpoint, your IP address, and the exact moment every message arrives for you. That is a per-message activity log held by a company you have no relationship with and we cannot see into. If that matters to you, do not enable push. Which company depends on the browser: Apple for Safari, Google for Chrome, Microsoft for Edge, Mozilla for Firefox — each sees the endpoint, the timing and the encrypted size, and none sees the text. Apple has said since December 2023 that it requires a court order for push records; that is its stated US practice, and we could not find what any of them do for a UK request.
What the payload does carry, since it is not nothing. A background notification can name the sender and the group it arrived in — and it does that without the name being in the payload. The payload carries a 16-character opaque ticket: twelve bytes of a keyed hash over the sender's account number and the conversation's, under a secret your own device generated. It is the same 16 characters wide for every push, so the encrypted payload is the same size for every push, which matters because a push service can always measure a payload it cannot read. Your device turns that ticket back into a name by looking it up in a table it built itself; nobody else holds that table, and the server that computes the ticket has never been told a display name.
The cost, stated plainly. That lookup table is stored unencrypted on your device, because the whole point is that it can be read while Haven is closed and locked. So turning on background notifications with detail above “Nothing” writes your contacts' display names and your group titles into this browser's storage. Setting notification detail to “Nothing” deletes that table rather than merely declining to read it, and the panic key, the duress password and the dead-man's switch all destroy it along with everything else.
"Hide presence" now stops the recording — but we still see you sign in
The usual way to build this setting is to mask the value on the way out and go on writing
it, and that is what Haven used to do: your last-seen time was stamped on every sign-in and
every dropped connection, and only NULLed by the queries that handed it to other users — so
an account that had asked not to be observed was still accumulating a complete record of
when it was awake, on our disk, readable by us. That is no longer true and the change is
worth being specific about, because "we stopped showing it" and "we stopped keeping it" are
different promises. Every site that stamps
users.last_seen — the socket disconnect handler and both sign-in paths — carries
AND hide_presence = 0 in the UPDATE itself, so with the setting on there is no
write; switching it on NULLs whatever was already stored, in that same statement; and
timestamps written before the gate existed are cleared once, at boot. A hidden account's
last-seen column is empty rather than merely masked, which is why the operator's own
activity dashboard undercounts and is annotated to say so.
The honest boundary, since this is that section of the page. It is not a cloak. If you
have armed the opt-in dead-man's switch, signing in stamps the DAY in a separate
last_login column that is deliberately not gated on this setting,
because it is what the switch counts down from and silently freezing it would disarm a
feature you armed on purpose. With the switch off, that column is empty. Your live connection is a
socket held open on our server, which no preference can hide from the machine at the other
end of it. And every session in your device list carries its own last-active time. What the
setting removes is the fine-grained, permanently accumulating record of when you were
awake; it does not make you unobservable.
It has not had a professional security audit
There is an automated test suite — it runs with npm test, needs no network,
and covers the security fixes described further down this page, including the group-privacy
bug and the key deletion in the box below it (node test/run.js forward takes
the adversary's position and requires every key the server still holds to fail to open a
retired one). That is a regression net, and a regression net is not an audit. Nobody outside this
project has reviewed the cryptography, the key handling or the server. We will say so on
this page until somebody has, and if a vendor tells you they have been audited, ask for the
report and its date and check whether it covered the part you care about.
There is no certification either — not ISO 27001, not SOC 2, not Cyber Essentials, not anything. The data processing addendum lists that absence in the same clause as the measures that do exist, because a procurement questionnaire is exactly where a service is tempted to answer this one loosely.
Forward secrecy is partial, opt-in, and off unless you switch it on
Signal, Wire and WhatsApp use a Double Ratchet, which derives a fresh key for essentially every message and recovers after a key is stolen. Haven's default does neither, and will not pretend to. What it has is a conversation key that changes on a schedule — every seven days by default, immediately when somebody is removed from a group or leaves one, and whenever somebody is added (a new member is given only the new key, and is not served anything sent before they joined) — plus a per-conversation setting that tells the server to delete your wrapped copy of each superseded key once a grace period has passed. Most of this box is what that setting does not cover.
Since October 2026 there is also an optional per-message ratchet, the conversation setting "Advanced Forward Secrecy Ratchet (Per-Message Re-keying)", off by default and turned on only after a confirmation that says what you give up. With it on, every message gets its own key from a hash chain; your device destroys each message's key 90 days after it first reads or sends it, and each conversation key that comes into use after the switch once it has been replaced and its grace period (at most 30 days) has passed. Turning it on also turns on the deletion setting above for you. What you give up: a message this device had not opened when its key was destroyed can never be read, and a device you sign in on later cannot read history whose keys have been retired from the server. Keep anything you need longer by exporting it first. What it is not: it is a hash ratchet, not a Double Ratchet, so somebody who steals a conversation key reads everything until the next rotation, it does nothing about the server's copies held by participants who left deletion off, and while it is on only text can be sent and messages cannot be edited.
It is off by default, and it was off for every conversation that existed before it shipped. Until somebody switches it on, the paragraph this box used to contain is still exactly true of your account: the server retains every historical conversation key, each version wrapped to each participant's long-term identity key, so an adversary who takes a copy of the database today and cracks your password at any point afterwards recovers every conversation the account has ever had, including the ones whose key you rotated away from.
Switched on, it buys a window rather than a clean slate. The current key is always on the server — it is how your own other devices read the conversation — and so is every superseded key still inside its grace period. On the shipped defaults that means a seizure today plus your password recovers everything since the last rotation and up to about five weeks before it. It is also per participant: if you turn it on and the other person does not, their copy of every key is still here, and the same seizure plus their password reads the whole thread anyway.
And a conversation that never rotates gets nothing. Only a superseded key can be deleted. Rotating a group re-encrypts the group's name, and the ability to write that name is the ability to rename the group, so rotation in a group is admin-only — which means a group whose admins are all inactive keeps one key indefinitely and has no forward secrecy at all. The app says so rather than letting the setting imply otherwise.
What it costs, stated before you turn it on rather than after. Messages sealed under a deleted key are readable only on devices that stored it first. A device left offline longer than the grace period loses that stretch permanently; a device you sign in on afterwards never had the key, so a new browser shows retired stretches as unreadable from the moment you set it up. Nobody can restore any of it, including us. The keys your devices keep are stored encrypted under a key derived from your identity secret, so a locked app cannot read them — but an adversary with the device and your password, or with it open in front of them, can.
And the deletion is "after the grace period", not "on the hour it expires". The sweep that removes retired keys runs on an idle tick, and it used to examine every superseded wrap on the instance on every one of those ticks — work that grows with the service and never drains, on the single thread that serves every message. It now walks the key timeline in windows and resumes where it stopped, so one tick costs its time budget rather than the size of the table. The price is stated where the code makes it: a key that becomes due behind the cursor waits for the cursor to come round, which on a large instance is a few hours against a grace period of thirty days.
It deletes keys, not messages, and it is not retrospective protection. The ciphertext stays here exactly as long as it would have anyway. Forward secrecy is forward-looking by definition: somebody who takes your key material now reads everything from now until the next rotation. And it does nothing whatsoever about metadata — who you talk to, when, how often, and how many times a conversation has rekeyed are all still ordinary rows in an ordinary table.
Concretely: on a default account your password is not protecting today's messages for today, it is protecting all of them, for as long as a copy of the database exists anywhere. Choose it on that basis. Building a ratchet correctly is hard and easy to get subtly wrong, and a half-right version would be worse than an honest absence — which is why the deletion setting is described here as a bounded deletion of key material and never as a ratchet, and why the per-message ratchet is described as exactly what it is: a hash ratchet, opt-in, with its costs stated before you switch it on. The threat model states every edge of it as a two-column ledger, and names the files to check it against.
The transcript chain proves nothing was taken out of the middle, and seven things it does not prove
A signature binds a message to its author and its conversation, never to a position in it — so on its own it left us able to drop a message, withhold one from a single participant, or replay an old one, with everything around the hole verifying perfectly. That is now closed for the middle of a range, and the way it is closed decides what it still leaves open. Every message carries a second Ed25519 signature over a payload holding no ciphertext at all: the conversation, the author, the key epoch, the sender's idempotency key, and the messages its author had already seen, each named by a hash of its own chain payload. Remove one and the messages that named it report a parent that is not there, and your app says so above the thread rather than rendering a tidy conversation with a hole in it.
It is a graph rather than a line, because two people who send at the same instant have each seen a history the other's message is not in. They name the same parent, the graph reconverges, and concurrency is not an alarm — an alarm that fires on every busy conversation is an alarm nobody reads.
What it does not do, in full, because a half-understood guarantee is worse than none. It does not say who emptied a message: nothing authenticates a deletion, so a row we blanked and a row its author deleted are identical. It does not see a message suppressed at the very end of a conversation, because nothing came after it to refer back to it — only a later message closes that. It cannot prove a conversation goes back as far as it should: serving you short pages moves the verified floor up rather than setting off an alarm, and the strip names the message it verified from and says older history was not checked. It says nothing about messages it cannot recompute a position for at all — history from before the chain existed, which reads "not checked" permanently, because a server able to chain it retroactively is a server able to forge it; and any message whose author has since deleted their account, where the sender column becomes NULL and the range reports itself partly checked rather than clean. It says nothing about content: the payload has no ciphertext in it, so rewriting the words of a stored message leaves every parent reference resolving and the strip still reading unbroken — content is the other signature's job, and each of the two is silent about the other's subject. It does not stop a false alarm from inside the room: the server refuses a malformed parent list and a chain signature that does not verify, but deliberately does not check that the ids a message names point at anything real, so a participant who wanted to could put the warning on everybody's screen with nothing removed — an extra lookup per parent on the busiest write path, buying nothing against us, since we hold the database and need not ask the handler. And it cannot decide whether the client drawing the verdict is the client we published: that is the first box in this section, and the chain is delivered by the machine it is checking.
The distinction the whole thing rests on: a delete, an edit, a disappearing-message expiry and a retention sweep all leave the row and its chain fields where they are — the three removals blank the ciphertext and leave a tombstone, the edit replaces the ciphertext and re-signs only the authorship signature, and a view-once burn runs the same statement as a delete. Because the chain payload never covered the ciphertext, all of them still hash to what their children named. A legitimate removal empties a node; censorship removes one.
Messages written before signing shipped carry no signature and never will. Producing them now would need a server able to make those signatures, which is a server able to forge them — so they are rendered as unsigned rather than presented as verified. That is honest without being conclusive: a message from an account that has not signed in since the change looks exactly the same.
The signing key is derived from the identity key already in your browser, which is why adopting it needed no re-key, no password prompt and no migration of anybody's stored key bundle. The cost of that choice, stated rather than buried: the two keys now stand or fall together, so whoever recovers your identity secret can both read your history and sign as you. It also means an identity key cannot be rotated while keeping a verifiable history — that needs a signed key-transition statement, an old key vouching for a new one, and there isn't one. Today a genuinely changed identity key means a new account.
And the direction people forget: signing removes deniability, and the chain removes a little more of it. Your signature over your ciphertext sits on our server, so anybody who later obtains the conversation key as well can prove to a third party that your account produced those words rather than merely asserting it — and the chain signature needs no conversation key at all, because its payload is the conversation, the author, the key epoch and a list of parent references, each an id and a hash. What that one proves is that your account was in a conversation and committed to having seen particular messages in it: a signed ordering, readable by anybody holding the database. Signal's protocol is deliberately deniable for that reason; ours is not, and the threat model lists it among the things Haven does not defend against.
A device that is already compromised
Malware on an unlocked, signed-in machine reads whatever that machine can decrypt. Auto-lock narrows the window; it does not close it. The same goes for the duress password and local wipe: they defeat someone using a borrowed or seized browser, not something already running inside it, and clearing browser storage is not forensic erasure of the disk.
What other software on the device can see
A web page cannot stop a screenshot, a screen recording, or an operating system reading what is on the screen. Native messengers can set a flag for the first two — Signal Desktop does on Windows, Threema Work on phones — and no browser exposes one; the proposal to add it to web apps was closed. So we do not claim what Threema Work can. The same is true of the assistants now built into phones and computers — Apple's Siri with on-screen awareness, Google's Gemini, Microsoft's Recall — which see what is displayed, and which no web app can opt out of.
What you can do, and what we control: set Notification detail to "Nothing", so the lock screen and any notification summary only ever read "New message"; turn on strict mode; add this site to Recall's list of excluded websites; switch off Apple Intelligence summaries and Gemini's app access for your browser. If your organisation needs screenshots blocked, that is a job for its device management, not for a web page.
Summaries, and what "on the device" means here
Where your browser has a built-in summariser, Haven can summarise a conversation with it. No server ever sees the text, ours or anybody's, and there is no enclave to trust — which is the difference from the summaries other messengers now offer, where the text leaves the phone and is processed in a data centre that is audited rather than absent. The model is your browser vendor's, running on your machine, and Haven does not offer the feature where the browser cannot do it locally.
If you think you are being targeted
The 2025–26 campaigns against messenger users — spyware delivered through WhatsApp, fake apps, and people posing as support staff to collect sign-in codes — mostly relied on three things. Nobody from Haven will ever ask you for a code, a password or a recovery key — the app asks for those on its own sign-in screen and nowhere else — and no message from us will send you to sign in anywhere but this site's own address. Anything that does is not us.
If you believe you are being targeted, the Access Now Digital Security Helpline helps civil society — journalists, activists, human-rights defenders — for free, around the clock, in several languages, and aims to reply within two hours after checking that you are somebody it serves. It is where Apple sends the people it warns. Its address is accessnow.org/help.
Screenshots, and anything past the screen
There is no reliable web API to block screenshots or detect tampering, so Haven has no toggle claiming to. View-once messages remove the persistent copy; they cannot stop someone photographing the screen. Disappearing messages and the Free tier's 90-day window delete the server's copy and cannot reach a device that already synced the plaintext.
Not everything is encrypted
Reactions, typing indicators and per-conversation settings such as the disappearing timer are stored in the clear. This page used to call them low-sensitivity metadata; an audit of the schema rejected that and the phrase is withdrawn. A reaction is stored attributed and timestamped, and a named person's reaction to a named message at a named time is content by any ordinary reading of the word. A starred message is a plaintext row saying which message you starred — private from other users, not hidden from the database. Push notifications carry no content at all, deliberately, because they travel through a third party's push service.
Two more that the disclosure panel names and this page used not to. Your TOTP secret is stored in the clear, because the server has to compute a code from it on every sign-in — that is what a shared-secret second factor is, and there is no version of it that is not. Anyone holding the database can generate your codes, so two-factor here protects you against somebody with your password and not against somebody with this table. A security key does not have that weakness: the server holds only its public half, which checks a signature and cannot make one, so a copy of the database generates nothing. It also cannot be read out to somebody on the phone, which is how most stolen codes are stolen. It sits beside the app rather than replacing it, so losing the key is losing a phone, not losing the account. And your username survives your account in the administrators' audit log: it is copied onto each administrator action when the action is taken, so the record can still answer "who deleted this account" after the account is gone.
An invite code is a bearer token
Whoever holds the string can create an account with it, whoever it was meant for. Only its hash is stored, so a database dump doesn't hand out working invites — but a code forwarded or screenshotted into the wrong chat is a working credential for whoever ends up with it. Single use, short expiry, revoke after use.
The worst thing we found in our own code
A security pass on Haven found a bug of exactly ANOM's shape, arrived at by accident rather than by design. A conversation could be flagged as a private two-person chat while actually containing three people. The interface renders a non-group as "you and the other person", so the third member was invisible — and when two users later opened what they believed was a fresh private chat, the system handed them that conversation. Two people with a silent third party holding the key, receiving everything in plaintext.
Both halves are fixed. We are telling you about it anyway, for two reasons. First, the encryption was flawless throughout and it did not help: safety numbers catch a substituted key, not an extra one. Second, it was found by reading the code again, not by an audit — which means the honest conclusion is that there is probably more, and that is the strongest argument we can make for why an audit is on the list and why we won't claim to have had one until we have.
Questions worth asking us, and anyone else
"What can you hand over if compelled?"
Ours, as a list: message ciphertext; every conversation key, current and historical, wrapped to each participant — minus the superseded ones a participant asked us to delete and the grace period has since removed, which are not ours to produce any more; the participants table, which is the social graph; message timestamps to the second; attachment sizes in bytes; last-seen times, except on accounts that hide their presence, where that column is empty; sign-in times; usernames, including those of deleted accounts wherever an administrator acted on one; the TOTP secret of any account with two-factor turned on, which is stored in the clear because the server computes codes from it; the coarse device label on each session, unless that account asked for it to be withheld; for every message written since signing landed, its author's signature and that account's public verification key, which together let a third party confirm which account produced a given ciphertext; and, since the transcript chain landed, a second signature per message naming the messages its author had already seen — an ordering of a conversation, signed by its participants. Not message content and not filenames, because we have no way to get at them. Group names only where the group predates sealed names — an older group still holds a readable one. And, for an account that has paid by card, an email address: there is no email column on the account, but the payment processor's notifications are kept here verbatim so their signatures stay checkable, and a card payment's notification carries what you gave the processor — your email address, and depending on what it asked for your name, postal address and the card's last four digits. We cannot send to it; we hold it. A free or voucher-paid account leaves no such record. Ask any vendor to answer this as a list rather than a slogan — and notice how much of ours is not the content. The same list is in the terms of service, where it is a term of the contract rather than a paragraph on a marketing page, and the warrant canary covers the demands we would not be allowed to tell you about.
"Has it been audited, by whom, and when?"
Ours: no. If a vendor says yes, ask for the report and its date, and check whether it covered the thing you care about.
"What happens when someone leaves?"
Removing someone from a group does not take away the key they already hold, in any system. Rotating the conversation key is the step that actually cuts them off. A removal or a departure marks the conversation as due for one straight away, and a nominated participant's browser performs it — the server cannot, because it has never held a conversation key. Haven also prompts the person doing the removing. In a group only an admin can rotate, so a group with no active admin stays on the key the departed member holds until one appears.
And when someone joins: adding a member to a group is itself a key rotation, so the newcomer holds no key from before they arrived, and the server does not serve them any message sent before they joined — their history starts with a "You joined on …" line. That filters what we serve; it cannot stop an existing member forwarding something older.
"What does your admin see?"
Ours: the door, not the rooms. An instance admin can control registration and mint invite codes — an operator capability that governs who may create an account at all, and not something any plan sells. What a paid plan buys is adding and removing accounts on your own subscription, and on Business and Enterprise, choosing how long the server keeps its copy of messages. Neither of those reads anything. There is deliberately no administrator who can read messages, because the server holds nothing readable.
Whether this is the right thing for you
If your adversary is a well-resourced organisation that specifically wants to know who you talk to, Haven is the wrong tool and you should use something else. That is the honest answer and it is at the top of this box rather than the bottom of it. We hold the social graph in plaintext, we have no Double Ratchet — only the opt-in hash ratchet and the partial, opt-in forward secrecy described above — we keep every old key unless somebody asks us not to, and the code that does your encryption is delivered by the server you would be hiding from. SimpleX has no account identifier at all and Session runs over an onion network. Both are free. Against that threat model they are simply better, and no price we charge changes that.
A hosted subscription is a reasonable answer for someone who wants end-to-end encrypted content, named accounts they control, larger files and rooms than a consumer app gives, and a server they don't have to operate — and who is trusting the operator not to be turned. That is an ordinary decision and a lot of people can make it safely. We would rather you buy it for the correct reason or not at all.
If you are protecting a source, or anything where being wrong is catastrophic, look hard at Signal first. It is free, properly audited, and has the automatic per-message forward secrecy Haven doesn't — a bounded, opt-in deletion of old keys is not the same guarantee and is not offered as one. If it fits what you need, use Signal — we would rather say that than sell around it.
Running Haven on your own hardware is the third option, and the honest description of it is neither "the strongest way" nor "not worth it". The source is all here and readable, an instance you deploy runs the same code, and the server that ships you JavaScript would be one you control. It is also unsupported: no support obligation, no uptime commitment, no managed upgrades or backups or TLS or disk space — that becomes your job, along with the consequences of doing it badly. Hosted Haven asks you to trust an operator; self-hosted Haven asks you to be one. Which of those is safer depends entirely on how good an operator you are, and for most people the answer is not the flattering one.