Documentation

Unlisted, end to end.

Everything an organiser, attendee, door volunteer or self-hoster needs: how to use Unlisted, what it protects, what it costs, and how it works underneath.

Unlisted is RSVPs for groups whose attendee list would hurt people if it leaked: support groups, union organisers, journalists and their sources, activists. A small refundable deposit in shielded ZEC stops no-shows. The deposit hides who paid, the ticket is a secret that lives only on the attendee’s phone, and the refund goes back to an address only the attendee knows.

The result: the server never has a list of people to leak. Its whole database for an event is random ticket hashes and one-time refund addresses, and it deletes those when the organiser purges the event.

Hosting an event

  1. 01
    Create the event
    On Create event, set a name, time, capacity and refundable deposit (minimum 0.0005 ZEC). The place is public: for sensitive venues, write something like “sent on Signal after you RSVP”.
  2. 02
    Save your two private links
    You get a manage link and a door link, shown once. Unlisted stores only a hash of them, so it can't recover them. Keep them in a password manager. You can also choose to remember them on this device.
  3. 03
    Share the public RSVP link
    Send it however your group already talks. Anyone with it can RSVP until the event is full or you close RSVPs.
  4. 04
    Check people in
    Open the door link on the phone at the entrance and scan tickets. It works offline and syncs when the connection returns.
  5. 05
    End the event & refund
    On the dashboard, End event & refund sends everyone who checked in their deposit back. Deposits of people who didn't come are forfeited, and swept to your group's fund address if you set one.
  6. 06
    Purge
    Once refunds are done, Purge deletes every ticket hash, refund address and payment record. Only totals remain.

If everyone comes, there are no forfeited deposits to pay network fees. The dashboard will ask you to send a small top-up (about 0.001 ZEC, no memo) to the event address, and refunds then go out automatically.

RSVPing

You need a Zcash wallet that holds shielded ZEC and can pay a payment link with a memo, such as ZODL.

  1. 01
    Open the invite
    Paste the link on the home screen, or open it directly.
  2. 02
    Give a refund address
    In your wallet, open Receive and copy the shielded address (it starts with u1). It must be fresh: never one you used for another Unlisted RSVP. The page checks it can receive a private (Orchard) refund before you pay.
  3. 03
    Pay the deposit
    Scan the QR code or tap Open in wallet app. The amount and memo are filled in; don't change them. Confirmation takes about a minute.
  4. 04
    Keep your ticket
    Your ticket appears as a QR code. Save the image: it is stored only in this browser, and it is the only way in and the only way to get your deposit back.
  5. 05
    Show it at the door, get refunded
    After the event, the deposit returns to your refund address.

Your tickets are listed on the home screen under Your tickets, on this device only.

If your payment is returnedWhy
The event was fullSeats go in block order. Returned minus 0.0001 ZEC.
The amount didn't matchEvery RSVP must pay exactly the deposit, so payments look identical on chain.
RSVPs had closedThe organiser closed RSVPs or the deadline passed.
The refund address was used beforeReusing an address would link your attendance across events.
The memo was changedWithout the memo there is no ticket. A payment with no readable refund address can't be returned.

At the door

Open the door link on a phone and tap Start scanning. Each ticket shows Welcome in, Already checked in, Not a valid ticket or Wrong event. You can also paste a ticket code by hand.

The door keeps a copy of the event’s ticket hashes, so it keeps working if the connection drops. It queues check-ins and syncs them when it’s back online. It never sees names or refund addresses, and it records whether a ticket was used, not when.

How it works

  1. 01
    One wallet account per event
    Each event gets its own account from its own random seed (encrypted at rest), with an Orchard-only (shielded) address and a birthday at the current block. Purging the event deletes the seed.
  2. 02
    A secret on the attendee's phone
    The browser makes 32 random bytes and keeps them. Only SHA-256 of that secret leaves the device.
  3. 03
    The memo
    The payment carries one encrypted line: UNL1 <event> <ticket hash> <refund address>. Only the event’s wallet can read it.
  4. 04
    The wallet reads it
    A light-client wallet syncs from lightwalletd, fetches the full transaction over Tor to decrypt the memo, and the API records the hash and refund address.
  5. 05
    The door checks a hash
    The ticket QR holds the secret; the door hashes it and looks the hash up.
  6. 06
    Refunds stay in Orchard
    Refunds are batched Orchard-to-Orchard sends with the FullPrivacy policy, so no amount or recipient is revealed on chain.

Why only Zcash can do this: cards and PayPal give the organiser a name; Bitcoin and Ethereum put payer and refund on a public ledger. Shielded Zcash hides sender, receiver and amount, and carries the refund instructions inside the payment.

Deposits & fees

DepositSet by the organiser, minimum 0.0005 ZEC. Refunded in full to everyone who checks in.
Returned paymentsRefunded minus 0.0001 ZEC, so junk payments can't drain the event's fees.
Network fees for refundsPaid from forfeited deposits and organiser top-ups, never from refunds.
When a refund counts as paidOnce its transaction is mined with 3 confirmations. One that expires unmined is sent again automatically; it is never paid twice.
No-show moneySwept, minus the network fee, to the organiser's no-show fund address once refunds have confirmed. Without an address it waits in the event wallet, and the event can't be purged until it is swept.
When refunds can go outA deposit becomes spendable after 10 confirmations (about 12 minutes). Refunds queued earlier simply wait.
Who holds the moneyUnlisted's wallet holds deposits until the event is settled. Keep deposits small: the price of a coffee.

Privacy

The server never stores names, emails, phone numbers, check-in times or ticket secrets, and the API keeps no access logs. (Your hosting provider’s edge may still log IP addresses; use Tor for high-risk events.) Organiser and door links keep their secret after the #, which browsers never send to any server. Attendees never send the server anything that points at their ticket.

Read the full threat model for what is stored, every known leak and how it is closed, and what you are trusting.

Self-hosting

Unlisted is three pieces: this web app, a FastAPI service, and unlisted-wallet, a light-client Zcash wallet built on librustzcash that syncs from a public lightwalletd server over Tor, so no full node is needed.

# On a fresh Ubuntu server, as root: wallet + API + HTTPS, secrets generated for you
curl -fsSL https://raw.githubusercontent.com/linoxbt/cashr/main/deploy/install.sh | bash -s -- api.example.com
# Back up the wallet volume and, separately, UW_SEED_KEY. Both are needed to move funds.

Put HTTPS in front of the API, then deploy web/ to Netlify or Vercel with NEXT_PUBLIC_API_URL set to the API’s URL. Run exactly one API process: it refuses to start if another holds its database. Full settings are in docs/DEPLOY.md.

API reference

JSON over HTTPS. Organiser and door routes take Authorization: Bearer <token>.

Method & pathAuthDoes
GET /meta—Chain mode, network, current block, reject fee.
POST /events—Create an event. Returns the event, the admin token and the door token (once).
GET /events/:id—Public event details and counts.
GET /events/:id/tickets—Every ticket hash, plus returned payments. Statuses appear only once the event settles.
GET /events/:id/adminAdminEverything stored for the event, the wallet balance and payouts.
POST /events/:id/admin/:actionAdminActions: close, reopen, settle, retry, purge, scan. Purge refuses while money is left in the event wallet.
POST /events/:id/admin/fundAdminBody {"address"}. Set the no-show fund address; a settled event’s leftovers are swept to it.
GET /events/:id/door/manifestDoorConfirmed ticket hashes for offline checking.
POST /events/:id/door/checkinDoorBody {"ticket_hash"}. Result: ok, already, unknown or event_closed.
POST /events/:id/door/syncDoorUpload check-ins recorded offline.

FAQ

Can the organiser see who came?

No. They see how many tickets were checked in, never whose. The deposit's sender is hidden even from the event's own wallet.

What if I lose my ticket?

It can't be recovered: no one else has the secret. Save the ticket image when you RSVP.

What if the organiser loses their links?

The event keeps working for attendees, but no one can settle it. Funds stay in the event's wallet account, which only the server operator can move.

Can I use a transparent (t-address) balance?

Paying from a transparent balance exposes your address. Pay from your shielded balance; refunds always go to a shielded address.

Why does my ticket say Awaiting payment?

Your payment needs one block (about 75 seconds) to confirm. If it never confirms, check the amount and memo were unchanged.

Can a seized server be used to get in?

No. It holds only hashes of tickets, and the door needs the secret behind the hash.

Home
--:--
Chain: …
Docs · Unlisted