Haven Docs

Backend and sync

Haven's server footprint is deliberately small, and none of it sits in the blocking path. The app protects a device that is permanently offline.

Three server-side pieces exist, each with a narrow job.

1. The blocklist distribution endpoint#

Serves the current blocklist version and the signed delta described in Blocklist distribution.

The request the device sends contains only the version number it currently has. No hostname the user visited, no identifier, no account, no device information. The endpoint cannot learn anything about what a user browses, because it is never told.

The response is a pre-signed envelope; the signature is produced offline by the intelligence backend and verified on the device against a pinned key.

2. The gambling-intelligence backend#

A separate TypeScript project that produces the rules. It never talks to user devices — its output is a signed file.

It covers:

ComponentJob
ScannerFetches candidate pages, with an SSRF guard on outbound fetches
Evidence and fingerprint extractionTurns a page into structured signals
Classifier and matchersDomain normalisation, fuzzy, phonetic and mirror-domain matching — the same conceptual layers as the client
Licence and regulation intelligenceJurisdiction and licence data
Mirror profilesTracking the numbered mirror-domain families operators cycle through
Review queueHuman review before a rule ships
Delta generator, key manager, signerProduces and signs the versioned delta
Welcome emailThe account-created message

The signing private key lives only in the backend's own secret store, never in the app and never in version control.

3. The account and progression backend#

A hosted Postgres project (Supabase) providing four things, all optional:

  • Authentication — email/password and Google, with the confirmation and password-reset emails.
  • Profiles — a minimal row: name, language, notification preference, and a separate, explicit marketing-email consent with its own timestamp, never defaulted to true.
  • Entitlements — the record of who has Pro Plus.
  • Progression — the daily streak and cosmetic unlocks.

Row-level security#

Row-level security is enabled. A signed-in user can read and update only their own row, enforced in the database against the identity in their token rather than by client cooperation.

Every call the app makes to this backend is scoped to the caller by the token, including the bodyless ones — the user id is taken from the token server-side, never from a request parameter. A modified app cannot ask for someone else's data, and cannot ask for an entitlement it does not have, because the client role has no write path to the entitlement tables at all.

The administrative service credential that bypasses row-level security exists only server-side and is never present in the app.

Server-side functions#

A small number of server functions handle the operations that need more authority than a client should ever hold:

FunctionWhy it is server-side
Claim the daily streakThe date must be the server's, or the streak is a device-clock exercise
Sync cosmetic unlocksDerived from server-owned progression
Read own entitlementsReads records only the server writes
Delete accountErasing an auth row needs authority the app must never hold
Payment-processor webhookTurns a real payment into real access, server to server
Administrative grant / revokeOperated from a dashboard, never from the consumer app

The administrative functions are deliberately absent from the app. Shipping a grant button in a binary only advertises the door.

The payment webhook#

The device never tells the server that a purchase happened. The payment processor does, over a server-to-server call authenticated with a shared secret compared in constant time.

Two behaviours in it are worth noting because they are easy to get wrong:

  • Access is driven by the expiry, not the event type. A cancellation only means auto-renew was switched off; the user has paid through the end of the period and keeps access until then. Revoking on cancellation would be taking away something someone paid for.
  • A transfer between accounts revokes the old account's access as well as granting the new one, so one subscription cannot grant Pro twice.

4. The static site#

The public site also serves the two pages that must exist outside the app: the password-reset landing page and the account-deletion request page, plus the privacy policy and terms.

Configuration degradation#

Every server-side dependency is optional at build time, and the app degrades honestly rather than failing:

Not configuredResult
Accounts backendLocal-only accounts; everything on-device still works
BillingA local mock paywall; the app still builds and runs
Google sign-inThe Google button reports itself as not configured

The gambling block is unaffected by all three. It is fully local.

What the backend never receives#

Stated plainly, because it is the question that matters most for an app in this category:

  • No browsing history, no visited domains, no DNS queries
  • No page content
  • No list of installed apps
  • No accountability-partner details
  • No financial figures, budget entries or expense log
  • No block log, no learned-sites list, no personal filters

See Data handling for the complete inventory of what is sent.