๐Ÿ”’ Encrypted notifications only you can read

Pick a topic URL, share it with a phrase you only tell in person. Post messages to the URL; subscribers decrypt with the phrase. The server stores opaque ciphertext forever — it never sees a single word of your messages.

XChaCha20-Poly1305 + Argon2id, identical to the QEV vault format. All encryption happens in your browser via libsodium. Zero server-side plaintext. Zero tracking.

๐Ÿš€ Try the live demo

A server-side auto-publisher encrypts a random message every 2 minutes and posts it to a public topic. Click below to subscribe and watch messages arrive โ€” decrypted locally by your browser with a public demo phrase. Enable Android push after subscribing to feel a notification every 2 minutes.

What's "public" about the demo phrase?

The demo topic is qira-demo-feed and the demo phrase is qira demo feed public heartbeat 2026. Both are printed here in plain text on purpose โ€” anyone who loads this page can subscribe. A real topic uses a phrase you tell someone in person; the server still can't read plaintext because the crypto runs in your browser.

Publish

What the server actually sees

The server sees: the topic URL, a timestamp, a random message ID, and an opaque ciphertext blob. It does not see the message content, the phrase, or anything derived from them. Argon2id stretches the phrase with ~100 MiB of memory and 4 rounds before deriving the AEAD key; brute-forcing a single-password-protected vault is prohibitively expensive if your phrase is strong.

Subscribe

Inbox

Tap Show raw ciphertext โ–ธ on any message to see the actual JSON envelope the server stored. The bytes you see there are what the relay operator (us) sees โ€” nothing more.

Start watching a topic above to see messages here.

๐Ÿ› ๏ธ Decrypt this anywhere (no special tool required)

Each message is a self-contained QIRA-NOTIFY-V1 envelope: Argon2id-derived wrap key + XChaCha20-Poly1305 for the payload. Three ways to unlock any envelope you copied above:

  1. In this browser โ€” already happens above when you watch a topic with the phrase entered.
  2. On the Vault page โ€” open /vault/, scroll to Step 1 โ€” the vault file, paste the JSON into the text box, type the phrase, click Open it!. Works for any Notify JSON on any device with a browser.
  3. Offline with Python โ€” ~30 lines + pynacl, shown below. Same math, same result, no server contact at all.
#!/usr/bin/env python3
# pip install pynacl
import base64, json, sys
from nacl.pwhash import argon2id
from nacl.bindings import (
    crypto_aead_xchacha20poly1305_ietf_decrypt as aead_decrypt,
    crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
)

def b64ud(s):
    s += "=" * (-len(s) % 4)
    return base64.urlsafe_b64decode(s)

env = json.load(sys.stdin)
phrase = input("phrase: ").encode()

salt = b64ud(env["kdf"]["salt"])
wrap_key = argon2id.kdf(
    crypto_aead_xchacha20poly1305_ietf_KEYBYTES, phrase, salt,
    opslimit=env["kdf"]["opslimit"], memlimit=env["kdf"]["memlimit"],
)
vault_key = aead_decrypt(
    b64ud(env["wrap"]["wrapped_key"]),
    env["wrap"]["aad"].encode(),
    b64ud(env["wrap"]["nonce"]),
    wrap_key,
)
plain = aead_decrypt(
    b64ud(env["content"]["ciphertext"]),
    env["content"]["aad"].encode(),
    b64ud(env["content"]["nonce"]),
    vault_key,
)
print(plain.decode())

Run: python decrypt.py < envelope.json, paste the phrase. If you can decrypt offline, you've proven the server is dispensable โ€” the crypto stands on its own.

Security model in 30 seconds

Threat you're protected from: the relay operator (us) reading your notifications; an attacker who breaches the server reading past messages; a nosy coworker who knows your topic URL but not your phrase.

Threats you're NOT protected from: someone who steals your phrase; a keylogger on your device; a weak phrase (less than ~4 random words — pick carefully); metadata (the relay sees topic names, timestamps, ciphertext sizes). If any of those matter, use QEV's device-pairing flow instead — which does give you authenticated sender + forward secrecy.