FACTBOX

← Explore

Privacy Policy

Effective 4 September 2026 Applies to: factbox.app

Factbox is a subscription you read on the web, and it now has accounts and a server behind them. Most of what it remembers about you is still written into your own browser, where clearing site data is the whole of deleting it. Some of it is not: an account, the answers you gave when you started, what you have read, and a payment all live somewhere your phone cannot reach. This page keeps those two piles apart — what is written where, what leaves the browser, which companies receive it, and how to be rid of each.

The short version

01 · Who this is from

Factbox is published by Iris Monet Labs LLC. In this policy "we" means that company, and "the site" means the pages served from factbox.app. Questions go to hello@factbox.app.

This policy covers the site and the small backend behind it. It does not cover Stripe, whose pages take the payment, or the sites you reach by following a link out of the artwork credits. The other companies involved are named in section 09.

02 · What the site stores in your browser

These are the exact keys, as written by the code. Nothing on this list is read off your device by anything other than the site's own code running in it, and while you are signed out none of it leaves at all. Signed in, three of these keys are mirrored to your account and say so: fb_acct_v1 (section 07), and fb_read_v1 and fb_saved_v1 (section 08). Your email address is also separately re-entered at Stripe (section 06).

Every localStorage key the site's own code writes
KeyWhat it holds
fb_unlocked_v1Whether this browser has access without an account. The value is the single character 1, or nothing at all.
fb_pass_v1Your restore token, so the restore link can be rebuilt on demand. See §06.
fb_passsrc_v1Where that access came from: stripe, restore or local.
fb_read_v1Your reading memory, as one small JSON object: per story, the furthest card you reached, how many cards that story had, a whole-second timestamp of when you last touched it, and a finished flag. Capped at 60 stories and about 20 KB; the oldest entries are dropped first.
fb_saved_v1Stories you saved, as a list of story id and a whole-second timestamp, newest first. Capped at 200 entries and about 8 KB.
fb_saveprobeWritten and immediately deleted once per page load, purely to find out whether this browser will accept a write at all.
fb_owner_v1Set only if someone unlocks the site with the owner passphrase. It exists so the person who makes the site can read it on a new phone without paying.
fb_acct_v1What you told the opening questions and the sign-up funnel, as one small JSON object: a randomly generated local id, the email address you typed, your first name if you gave one, the subjects you picked, why you are here, what usually stops you reading, how much of a day goes to scrolling, who you want to be a year from now, the minutes a day you chose (or a marker meaning you asked us to choose), which plan you chose, whether you finished the questions, and when the record was made. It also holds the answers to an earlier version of these questions, where they were given: what draws you to history, which of three "sounds familiar" statements you ticked, the streak you picked and the plan questions' three yes/nos. Every one of these is a value from a fixed list written into the page; none of them is text you typed except the email address and first name. Capped at 700 bytes.
fb-soundWhether you switched a story's ambient sound on or off, so it does not have to ask again.
fb-sound-hintSet once, after you have been shown the note about your phone's silent switch, so it is never shown twice.
fb_analytics_optout_v1Set to 1 if you press the off switch in §05. It is the reason the analytics scripts do not load on your next visit, so deleting it turns them back on.
fb_support_last_v1The time you last sent a message from the support page, as a number of milliseconds. It is there so a double tap does not send the same message twice.
fb_cache_owner_v1The Firebase user id whose reading and saves the two keys above are currently holding. It is how the site knows not to show one person's reading to the next person who signs in on the same browser, and it is emptied on sign-out.
fb_analytics_notice_v1Set once you have been shown the note about analytics, so it is not shown again.
fb_access_seen_v1Up to twenty characters: how access arrived, plus the first eight characters of the token it arrived with. It exists so a single unlock is counted once rather than on every page. The token itself is never sent anywhere.

Four further values are kept in sessionStorage, which your browser discards when you close the tab. fbx_corrected_v1 is set after the page has reloaded itself once to correct a paywall it drew before your subscription had loaded, so it can never do that twice. fbpg_pulled_v1 does the same job for a shelf drawn before your account's reading had arrived. fb_auth_redirect_v1 is set while you are away at Google's sign-in page, so the site knows to expect you back. fb_live_v1 holds the Firebase user id this tab has actually seen signed in, so a page cannot draw personal state on the strength of a stale hint.

Story ids are strings like 07B. With the single exception of fb_acct_v1, no key on this list contains a name, an email address, or anything else that identifies you.

PostHog, Google Analytics and Firebase each keep storage of their own in your browser, under names they choose rather than ones written here. Sections 05 and 09 say what those are for. Clearing this site's data removes them along with everything above.

03 · Cookies

The site sets first-party cookies under four names: fb_unlocked_v1, fb_pass_v1 and fb_passsrc_v1 — the three access values above — and fb_acct_v1, which is mirrored only while it stays under 700 bytes and is silently skipped if it grows past that. They are set for one year on path=/ with SameSite=Lax, and with the Secure flag when the page is served over HTTPS.

The reason is specific. Most readers arrive from Instagram or TikTok, and those apps open links in an in-app browser that routinely throws away localStorage between sessions while keeping cookies. Without the cookie, a person who paid inside Instagram would be asked to pay again the next day. The site reads whichever of the two stores still has the value, and repairs the other.

Your reading memory and your saved stories are never put in a cookie. They are far too large, and a cookie rides along on every request — which would mean sending your reading history to the web server on every page load. They stay in localStorage. Signed out, that is the end of them; signed in, they are written to your account by a request that goes to Firestore and nowhere else, which is section 08.

Those four are the ones our own code sets. They are functional: they remember a purchase you made and what you typed into the funnel. They do not follow you to other sites and they feed no advertising system. The analytics services in section 05 set cookies of their own, on this domain, which is what an identifier for a returning reader is.

04 · Signing in

There are two ways in: Google, and an email address with a password. Both are handled by Google Firebase Authentication. We never see your password — Firebase holds it, and the site has no field, no code and no storage that could keep one. Signing up with an email address makes Firebase send you a verification message. Asking for a password reset makes Firebase send you another. Both leave Google's servers, not ours.

Signing in with Google hands Google the fact that you are signing in to Factbox, and hands us the email address, display name and profile photo on that Google account.

Phone sign-in was offered on an earlier version of these pages. It is gone: there is no phone field, no SMS code and no reCAPTCHA on the site.

05 · Analytics

Every page sends events to PostHog, a product-analytics service hosted in the United States, and to Google Analytics 4 through Firebase. Both get the same events. It is how we find out which stories people finish and which ones lose them.

Running two is a duplication, and it is meant to be temporary: they are being compared on the same traffic so that one can be dropped. Until then your visit is measured twice. The switch below turns off both.

Analytics is only transmitted from factbox.app and www.factbox.app. On any other host the code loads, runs and sends nothing, so a test build cannot report itself as a reader.

What is attached to you

Once you sign in, the events are no longer anonymous. The site calls PostHog's identify with your Firebase user id, which ties this browser to your account and joins the reading you did before you signed in to the same person. From that point every event also carries three properties: whether you have an account, whether you are a subscriber, and which of admin, subscriber, legacy or none your access is. Signed out, no user id is sent and those three still are.

Both services also keep an identifier of their own in your browser, so two visits from the same browser are recognised as the same reader. That is true whether or not you have an account.

The opening questions also keep a short random code of their own in this browser, so that one run through them can be told from another and we can see where people stop. It is generated by your browser, it is not derived from anything about you, it is never sent to Stripe or to anyone else, and clearing this site’s data removes it.

Where the requests go

PostHog is not reached at posthog.com. The site sends its analytics to factbox.app/ink/…, a path on this domain served by a Cloudflare Worker that forwards it to PostHog. This is deliberate, and the reason is worth stating plainly: blocker lists match PostHog by hostname, and a request to factbox.app matches no list. A content blocker that blocks PostHog will not stop these events. The off switch below will, and blocking factbox.app/ink/ will.

Two things the Worker does are worth naming. It strips the cookie header before forwarding, so the site's own cookies — including Firebase's — never reach PostHog. And it passes your IP address on in an X-Forwarded-For header, so PostHog places you in a country rather than in a Cloudflare datacentre. If the Worker does not answer, the site falls back to PostHog's own hosts and the events go there directly.

Google Analytics is not proxied. Those events go to Google.

What the events say

This is the complete list of event names, grouped. Every one of them also carries the three properties above, and your user id if you are signed in.

None of these carries your email address, your name, or anything you typed into a box. Your Firebase user id is on all of them once you are signed in, and that id is the thread back to the account record in section 07.

Who here can see it, and what they see

The accounts marked as administrators — the people who run Factbox — can open an internal dashboard. Everything on it is counted rather than named: how many people opened a story, how far they scrolled, how many subscribed. There are three exceptions, and they are deliberate rather than accidental.

One view on that dashboard lists readers by email address. For each reader it shows the email address on their account, which stories they opened, the furthest card they reached in each one, whether they finished it, and when they were last reading. If you have no account you appear on it with no email address, as “anonymous”, with the same reading beside you.

A second view shows how long each reader spent on each card. For each reader — by email address where they have an account, anonymously where they do not — it shows every card they were measured reading, the page they read it on, how many seconds it was on their screen, and a total for that reader. This is the card_view data described above, listed per person rather than added up.

A third view shows one reader’s activity in order. Choosing a reader from those lists shows what that one person did, oldest first, within a window of at most 31 days: the pages they opened, the stories and cards they read and for how long, the buttons they pressed by name, where they stopped, and the time between each thing and the next — so a gap of a day between two visits is visible as a gap. It is bounded to at most 500 events and it shows nothing that is not already in the list of events above. It does not show the text of any error message, and it does not show your country.

The email address is not in the analytics and is never sent there. What PostHog holds is the Firebase user id described above. The dashboard joins that id to the email address Firebase Auth holds (section 07) at the moment an administrator asks, on our own server, and stores the result nowhere. Nothing about this changes what is collected, what is sent, or who it is sent to — it changes who here can read it back with a name on it, and that is the part worth telling you.

Administrators can also see a count of readers by country. As section 06 explains, PostHog works out an approximate country from your IP address at the moment an event arrives. The dashboard shows that as a list — how many people and how many story opens came from each country — and nothing finer: no city, no region, no IP address, and no country shown beside any individual reader. It is a count of a place, never a place attached to a person.

The off switch below covers this as well: no events, no rows on that dashboard.

Turning it off

Press the button. It takes effect immediately in this browser, is remembered, and stops both analytics scripts from loading at all on your next visit — not merely from reporting. Turning it back on takes effect when you next load a page.

Every analytics call on the site is wrapped, so a browser that refuses to load either script behaves identically to one that loads both.

06 · Payment, and the restore link

Subscriptions are taken through Stripe, on a checkout page that Stripe hosts. You leave the site to pay and come back when you are done. Your card number, your billing address and your name are typed into Stripe's page, not ours. They never touch our pages and we never see them. Stripe is responsible for what you enter there, under its own privacy policy.

Two things are added to that checkout address when you go to it. The first is your email address as Stripe's prefilled_email, so the payment page's email field is already filled in and you do not type it twice; you can change it there. The second is your Firebase user id as Stripe's client_reference_id — or, if you are not signed in, the random local id from fb_acct_v1. That is the entire link between a payment and an account: it comes back on Stripe's webhook and is what lets your subscription be written under your record. Nothing else about you goes with them.

When Stripe reports a successful payment, our webhook writes the subscription under your account. What it stores is in section 07.

The checkout's return address carries a success flag and a checkout session id. That session id becomes your restore token, stored as fb_pass_v1, and your restore link is that token on the end of a factbox.app address. It moves access from the browser you paid in to another phone. Signing in does the same thing and is the route we would rather you used; the restore link remains for anyone who subscribed before accounts existed.

The restore link is a bearer token. Nothing checks it against a server, so possession is the entire proof. Anyone you forward it to gets access, and so does anyone who reads it over your shoulder or finds it in a screenshot. Treat it like a key, not like a receipt.

It contains no personal information: it is a random-looking string plus a four-character check that tells a mistyped link apart from a real one.

07 · Your account, and what is held under it

An account is a document in Google Firestore, keyed by your Firebase user id. Four things write to it, and each writes somewhere different.

Four of the six questions at the start of signing up — why you are here, what usually stops you, how much of a day goes to scrolling, and who you want to be a year from now — are not in that record and are not sent anywhere. They are stored in this browser only, under fb_acct_v1, because the rules that fix the shape of the document above do not name them. Choosing "let Factbox decide" rather than a number of minutes also stores nothing there. What does leave, for these four, is the measurement in section 05, which records which answer was tapped without being tied to that record.

Story text for subscribers is served by a function of ours that verifies your sign-in token and reads the subscriber flag itself, rather than believing anything the browser says. Its ordinary server logs are Google Cloud's, under Google's terms.

08 · Your reading, and where it now goes

This section changed. An earlier version of this policy said your reading never left your browser. That was true of an earlier version of the site and it is no longer true, because reading now follows an account rather than a device. What follows is what the code does today.

Your reading progress is: which stories you opened, how far into each you got, how long ago you last touched each one, which you finished, and which you saved. Signed out, all of it is written to your own browser and to nothing else — we do not have it and no page has an endpoint to send it to.

Signed in, the same information is also written to customers/{your id}/profile/reading in Firestore, under your Firebase user id. It holds a story id against four numbers — the furthest card, how many cards that story had, a whole-second timestamp, and a finished flag — plus the list of stories you saved, and counts of both. It holds nothing else; the shape is fixed in our database rules and capped in size there. This is what carries your place in a story to a second phone, and it is the reason signing in on a friend's phone does not show them your reading: the browser's copy is tagged with the account it came from and is thrown away when a different account signs in.

The trade has moved and is worth stating plainly. Your reading is now restorable — sign in anywhere and it comes back — and in exchange it is now something we hold, and so something that could be demanded of us or leak from us. Clearing your site data no longer deletes it; section 11 says how to have it deleted. The one thing that leaves in both cases is the measurement in section 05, which says a story was opened and for how long, not a list of what you have read.

What you write on the support page, and in the cancellation note

What you type into the two boxes on the support page does leave your browser. Pressing Send posts what you wrote to a function of ours, which writes one document into a support collection that we read in the Firebase console. The document holds six things and no others, and the function builds every one of them itself rather than accepting them from the page: the time, the reply address you typed if you typed one, which of the two boxes it came from, your message, the page you sent it from, and your Firebase user id if you were signed in. Your IP address is not stored on the message. Messages are capped at 4,000 characters.

It is counted, though, and that changed on 4 September 2026. To stop one person flooding the box, the function keeps a running count per address. What it stores is not the address: it is a keyed hash of it, cut to 96 bits, under a salt that rotates daily — so yesterday’s counter cannot be matched to today’s, and neither can be turned back into an address. An IPv6 address is cut to its first half before that. The counter document holds numbers and nothing else — no message, no address, no account — and a Firestore expiry policy deletes it automatically. It is never joined to your message: the two live in separate collections and share no key.

If that post does not go through, the page offers you a link that opens the message in your own mail app instead, addressed to hello@factbox.app, with what you wrote already in it. Nothing is sent that way until you send it yourself. If you are signed in, the reply box is filled in with the address on your account; you can change it or empty it.

09 · Hosting, and every other company involved

The pages are static files hosted on GitHub Pages. Like every web server on earth, it receives the ordinary request information needed to send you a page: your IP address, the address you asked for, your browser's user agent, and the time. That handling is GitHub's, under its own privacy policy. We do not receive or keep those logs.

Loading a page also makes your browser reach these, each of which necessarily sees your IP address:

Every image is served from this domain. Artwork is downloaded from Wikimedia Commons once, in advance, and re-hosted here, so reading a story sends no request to Wikimedia and tells them nothing about you. Following a source or licence link from the credits page is an ordinary outbound link, and that site then sees you the way any site you visit does.

10 · Children

Factbox is not aimed at children under 13, and we do not knowingly collect anything from them or knowingly let them hold an account. If you believe a child has signed up, write to us and we will delete the account, anything sent from the support page, and everything else held under it.

11 · Your rights, and how to use them

Depending on where you live — the UK and EU under the GDPR, California under the CCPA, and many places besides — you have rights to see, correct, export or delete the personal information a company holds about you. There are two halves to that here, and they work differently.

We do not sell personal information and never have.

12 · Security

The account side is Google's: Firebase holds the passwords and the sign-in tokens, and the rules on our database are written closed, so a signed-in reader can read their own record and no one else's, and cannot write the flag that decides whether they are a subscriber. Paid story text is not readable from a browser at all; it comes from a function that checks the flag for itself.

Two limits are worth being plain about, because the alternative is you finding out later. The restore link is a bearer token, as §06 says. And for a reader without an account, the access flag is a value in their own browser rather than a check against a server, so it is something the site remembers rather than something the site enforces. Free story text is served as a public data file.

Email is not encrypted end to end. Please do not put anything sensitive in a message to us.

13 · Changes

If the site starts doing something this page does not describe, this page is updated before that ships, and the effective date at the top changes with it. We do not backdate a change or edit one in quietly.

14 · Contact

Questions about this policy, a request to delete your account, or a request to delete something you have sent us: hello@factbox.app. A person reads it.

Factbox · Privacy Policy · Effective 3 September 2026. Describes factbox.app as it stands on that date.