Privacy
What this site collects, why, how long it keeps it, and the list of things it deliberately does not collect. Written from the code that does the storing, so the claims can be checked rather than trusted.
Last reviewed 2026-09-10
We store who you are and what you have access to. We do not store what you did with it: your progress, completion and scores are written to your own browser and never sent here. Payment is handled by Stripe on Stripe pages, so no card details reach this site at all.
This build counts page views through Vercel Analytics: how many people, which pages, nothing that identifies you. The script is served from this domain and its events post back to this domain, so you can confirm it in your browser network panel. It sets no cookie and builds no profile.
We do not sell, rent, or trade personal data, and there is no advertising network anywhere in this site.
This list is the same one published at /data, read from the same place in the code rather than retyped here, plus the answer that page does not give: how long each record lasts.
Your profile. Your email address and display name. So we know which seat is yours when you sign in. Kept while you have an account. Kept: For as long as you have an account.
Your entitlements. Which courses you have access to, and when that access started or ended. This is the thing the site actually checks when you open a course. If access is revoked or refunded, the row is marked revoked rather than deleted, so there is an honest trail of what you were owed and when. Kept: For the life of the account, which is longer than the access. Access ending does not delete anything: the row stays, marked with when it expired, and the sweep that reports lapsed seats never removes one. A revoked or refunded seat is marked revoked and kept for the same reason, so there is an honest trail of what you were owed and when. Deleting your account deletes these rows with it, and that is the only thing that does.
Grant history. A permanent record of every time course access was granted, and by what authority. The receipt trail behind your entitlements — it exists so nobody can quietly take back or rewrite what you were given. Kept: For the life of the account. Entries are added and never rewritten, because a receipt trail that can be edited is not one.
Payment events. When Stripe tells us about a purchase or refund, we keep five things about that message: the Stripe event id, the kind of event, the status of our handling of it, the reason the last attempt did not succeed if one did not, and when it was processed. So a purchase can never be lost or double-processed. Your card details never reach us — Stripe alone handles those. Kept: Indefinitely, and separately from your account. This is the payment ledger that stops a replayed message from Stripe granting a second copy of what you already bought, and it holds five things and no others: the Stripe event id, the kind of event, the status of our handling of it, the reason the last attempt did not succeed if one did not, and when it was processed. It holds no card details and no name, and it is not linked to your account, so deleting your account does not remove it: an event id we had forgotten is an event id we would process again. Stripe keeps its own records, for its own legally required period, under its own policy.
Deals and cohorts. If you came in through an employer, a class, or a group purchase, the record connecting your seat to it. So group access works and so we know who to talk to about renewing it. Kept: Indefinitely, unless someone deletes it. An access period ending does not delete anything: it makes the access inactive, and the sweep that reports lapsed seats never removes a row. No page or route on this site deletes a cohort or a deal today, so the record that your seat came through a group can outlast the access it granted. Deleting your account removes the row linking you to the group, and deleting the group removes every one of those rows with it. The deal itself records the buyer, the bundle and the price rather than you, and it stays either way.
Invite records. No stored invitation link. Inviting you creates a sign-in account for your email address, if you did not already have one, and a single-use sign-in link that expires on the sign-in provider's own setting. So a person named on a group purchase can sign in and claim their seat. Kept: There is no invite record, and that is the honest answer rather than a comforting one. Inviting you creates a sign-in account for your email address, if you did not already have one, and then produces a single-use sign-in link for it. The account is permanent: it is not attached to the group that prompted it, so neither claiming the invite nor the group access ending removes it, and it stays until you ask us to delete it. The link itself is separate and short-lived, expiring on the sign-in provider's own token setting (one hour by default here), and using it or letting it lapse changes nothing about the account.
Checkout sessions. For a group offer, the payment page we already opened for you, and when we opened it. We keep the Stripe session id, its web address, when it stops accepting payment, how long we asked Stripe to keep that page open when we started it, the access end date we showed you when it was opened (or the note that there is no end date), where that checkout has got to (being opened, open, or an outcome we could not confirm), and a one-time key we send to Stripe so a repeated attempt is answered with the page you already had instead of a second one. So coming back to the offer link reopens the checkout you already had rather than starting a second one you could also be charged for. Keeping the access end we showed you is what stops a reopened checkout quietly selling a different period than the one you read, and keeping the state and the key is what stops two clicks, or one answer we never received, becoming two payment pages. It holds no card details and nothing about what you were buying beyond the offer it belongs to. Kept: Until the next time you open the same offer, which replaces it, and then for as long as that one lasts. A session stops accepting payment within a day at the most, but the row is not swept afterwards: nothing here deletes an expired one on a timer. Deleting your account deletes it, and so does deleting the offer it belongs to.
How many separate places one seat is opened from. One purchase is one person, and this is the only way to tell a shared credential from ordinary use. The address is turned into a keyed token the moment it is read and the address itself is discarded; the token can be compared but not read back into a place. It lives in server memory and is never written to any database. Kept: 6 hours, then gone.
None of the following exists for an account that never touches the feature it belongs to. They are listed because "we store almost nothing" is not the same sentence as "here is everything we store".
Calendar connection. If you connect Google Calendar for scheduling, we hold the connection token encrypted, for as long as the connection is there. Only disconnecting ends it, and it is deleted when you do. Kept: Until you disconnect it. The stored record has no time limit of its own, so a connection you stop using is still there a year later. Disconnecting deletes it immediately and asks Google to revoke the token as well. The signed sign-in token your browser carries expires after 180 days, which ends that session and not the connection.
Calendar rate-limit counters. If you connect Google Calendar, requests to the calendar routes are counted so one account or one network cannot flood them: up to 12 connection attempts, 24 returns from Google, 60 status checks, 30 sync requests, 300 individual calendar changes and 10 disconnections, each per minute. Every counter is keyed by a one-way code made from your network address or your connection id under a key only our server holds - never the raw address - and each one is set to expire after its own minute, so nothing accumulates. Sign-in is counted separately, as the next record says, and nothing else on this site is rate limited. Kept: One minute from the first request in a window, set as the counter’s own expiry when it is created, so the store removes it rather than anything here deleting it. Nothing is kept between windows and there is no record of which windows you had.
Sign-in rate-limit counters. If you ask for a sign-in link, or open one, the request is counted so that one machine or one inbox cannot flood sign-in: up to 10 link requests from one network address in 15 minutes, up to 3 link requests for one email address in an hour, and up to 60 opened links from one network address in 15 minutes. Each counter holds a number and nothing else, and it is named by a one-way code made from your network address, or from your email address in lower case, never the raw address. That code is made under a key only our server holds whenever the site has one configured. If it has none, the code is made under a fixed name written in the site code instead, and then anyone who could read the counters could check a guessed email address or network address against them. The counters are kept in the shared store the site uses for this when one is connected, and otherwise in the memory of the server that answered the request. Kept: Fifteen minutes from the first request in a window for the two counts by network address, and one hour for the count by email address, set as the counter’s own expiry when it is created, so the store removes it rather than anything here deleting it. When no shared store is connected, the counter sits in the memory of the server that answered, with the same expiry: once that time passes it no longer counts against you, it is cleared out on the next counted request, and it is lost if that server restarts. Nothing is kept between windows and there is no record of which windows you had.
Your notebook. If you sync notes, what reaches our server is ciphertext we cannot read. The key never leaves you. Zero-knowledge is the design, not a setting. Kept: The synced record lapses 400 days after its last write, and every sync restarts that clock, so a notebook you keep using does not expire. Deleting it does not clear the key it lived under: the ciphertext and its IV are erased and replaced by a permanent marker that holds no note content, only a hash of your write key, a version number, a deleted flag, and the time. That marker is deliberately kept with no expiry of its own, because one that lapsed would let a delayed write from an old device recreate what you erased.
Two cookies, only if you use the tomato. Using the pomodoro timer sets two cookies on the jarrettheintz.com domain, both so the timer can follow you between this site and the academy, and both for 30 days. “lw_pomodoro” holds the timer state itself - which phase you are in, how long is left, how many rounds you have done - together with the profile id the timer files that state under. That id is your account id when you are signed in, or your email address when the academy handed us an email and no account id, so this is a cookie that names you and you can read it back in your own browser. “lw_pomodoro_guest” holds a random id made in your browser, so the timer can tell your state from somebody else’s before you have signed in; it is not your name, your email or anything we can look you up by, and it is generated locally rather than issued by us. Because they are set on the domain rather than on one page, your browser sends them with every request it makes to this site and to the academy - that is how a cookie reaches the second host, and it is the whole reason the timer can follow you there. Nothing on our server reads either one: the only code that opens them runs in your browser, and they are never written into our database. Browsing alone sets neither, which is the sentence at the top of this page, and it stays true because the id is held in memory until the first time you actually start the clock. Kept: Thirty days each, in your own browser, on the jarrettheintz.com domain. Being domain cookies, your browser sends them with every request it makes to this site and to the academy, which is how the timer follows you across the two. Nothing on our side stores or reads either one, and clearing your browser data ends both.
Practice answers. If you answer a practice question, that answer leaves this site, in two places, for two different reasons. First, the whole submission goes to the LearnEngine service, the one that reads it and writes the explanation you see next: the choice you picked, the reason you typed, and the hypothesis you gave if the question showed you the stem before the choices. It travels with a signed claim that carries who you are and your session, because LearnEngine has to know which learner and which attempt it is answering. Second, once that is done, four narrower things go to the LearnQuiz service that marks it: which question it was, which choice you picked, a yes/no flag the question carries (it is named “cold” and we only pass it along), and a one-way hash that stands in for you. That hash is made from your sign-in reference, your session, and the question, under a key only our server holds, so LearnQuiz can tell two answers apart without being told who you are; your email address and your name never travel to LearnQuiz. Neither copy is written into our database. If LearnEngine is not configured no explanation is generated and nothing is sent to it; if LearnQuiz is not configured nothing is sent to it either. Kept: Not kept here at all - this site writes none of either copy to any database, and nothing here reads either one back. The full submission - your choice, your reason, your hypothesis, and the signed claim identifying you and your session - is kept by the LearnEngine service for as long as it keeps it; this site keeps none of it. The narrower four-field copy sent afterward is kept for as long as the LearnQuiz service keeps it. Neither period is ours to set or promise: both are services this site does not control.
Your learning record is real, and it is not on our servers. It is written to your own browser and stays there, which is why the list below of things we do not store is not a claim that they do not exist.
This is the complete list, and it is generated from the code that does the writing rather than typed here, so a new one cannot quietly appear without this page changing. Each row names the exact key, so you can open your browser’s developer tools and check it yourself. Most of it never leaves your machine; the cookie rows are the exception, because a cookie is attached to requests by the browser itself, and each row says which it is.
Browser storage: learnsuite:scorm:v1…. Your learning record for one course: which lesson you are on, which lessons you have finished, your progress through it, whether the course is complete, and any scores it recorded. One entry per course, under a key made from your account and that course. It is never sent to this site, and there is no route here that could receive it. Kept: Until you clear your browser storage. Nothing here expires it.
Browser storage: learnweb-pomodoro:…. The pomodoro timer: the state of the clock (which phase, how long is left, how many rounds), a random guest id made in your browser when you are not signed in, and whether you have turned timer memory off. It is never sent to this site, and there is no route here that could receive it. Kept: Until you clear it, or until you switch timer memory off, which deletes it.
Browser storage: station-chill-mode. Whether you have switched the site into chill mode. It is never sent to this site, and there is no route here that could receive it. Kept: Until you clear it.
Browser storage: lw-rain-intensity. How heavy you set the rain on the desktop background. It is never sent to this site, and there is no route here that could receive it. Kept: Until you clear it.
Browser storage: learnweb.practice.stemFirst.v1. One display preference for practice questions: whether the question is shown to you before its answer choices. It is a setting, not an answer. It is never sent to this site, and there is no route here that could receive it. Kept: Until you clear it.
Browser storage: station.media.v1…. How you left the media player set: which lane you were in, whether it was compact, whether the transcript was open, which visualiser each lane uses, and whether you have seen and answered the lane warning. Preferences only - nothing about what was played. It is never sent to this site, and there is no route here that could receive it. Kept: Until you clear it.
Browser storage: station.media.audio.v1. The audio engine settings: volume and whether you muted it. It is never sent to this site, and there is no route here that could receive it. Kept: Until you clear it.
Tab storage (gone when the tab closes): learnos-booted. A flag saying the desktop boot animation has already played, so it does not play again every time you move around the site. It is never sent to this site, and there is no route here that could receive it. Kept: Until you close the tab.
Tab storage (gone when the tab closes): learnos-school-entry. A one-shot marker that you arrived at a course from the desktop, read once and removed. It is never sent to this site, and there is no route here that could receive it. Kept: Until you close the tab, and it is deleted the moment it is read.
Tab storage (gone when the tab closes): lw-enrollments-cache. A short-lived copy of the course list we just fetched for you, so moving between pages does not refetch it. Alongside the courses it holds the account it was fetched for - your email address, your account id, the display name on that account, and whether it is a staff account - so the page can tell whose list it is holding. All of it is the same information the page had already been shown. It is never sent to this site, and there is no route here that could receive it. Kept: Until you close the tab.
Cookie: lw_pomodoro. The pomodoro timer state again, as a cookie this time, so the timer can follow you between this site and the academy: which phase you are in, how long is left, how many rounds you have done. It also carries the profile id that state is filed under, which is your account id when you are signed in, or your email address when the academy gave us an email and no account id. This one does name you. Because it is a cookie, your browser attaches it to requests on its own; no code of ours reads it on the server. Kept: 30 days, on the jarrettheintz.com domain.
Cookie: lw_pomodoro_guest. A random id made in your browser so the timer can tell your state from somebody else’s before you sign in. It is not your name, your email, or anything we can look you up by. Because it is a cookie, your browser attaches it to requests on its own; no code of ours reads it on the server. Kept: 30 days, on the jarrettheintz.com domain.
Cookie: sidebar_state. Whether an admin sidebar was left open or collapsed, so the page can draw it that way before any script runs. It holds the word true or false and nothing else. No page on this site renders that sidebar today, so this cookie is not actually being set; the code that would set it is here, which is why it is listed. Because it is a cookie, your browser attaches it to requests on its own; no code of ours reads it on the server. Kept: 7 days, on this site only, if it is ever set at all.
Cookie: sb-…. Your sign-in session, under the name “sb-” plus our Supabase project reference plus “-auth-token”. It holds the two tokens that prove you are signed in - the short-lived access token and the refresh token that renews it - and nothing else: no name, no email, no course. It is set on the whole site, so your browser sends it with every request here, and our server reads it on every one of them. That is what being signed in is. Your browser cannot read it: it is marked HttpOnly, so no script on any page can see it. Because it is a cookie, your browser attaches it to requests on its own, and our server does read this one - that is what it is for. Kept: 30 days, on this site only, renewed each time your session is refreshed. Signing out empties it immediately.
Cookie: __Host-auth-challenge. Set only when you ask for a sign-in link, and only once the email has been sent. It holds 32 random bytes made for that one request and nothing about you: no email, no account, no course. The link in the email carries a one-way code made from that value, never the value itself, and when you open the link our server checks that the two belong together before it signs anybody in. That is what stops a sign-in link from working in a browser that did not ask for it, so a link somebody else requested and forwarded to you cannot sign you in to their account. It is marked HttpOnly, so no script on any page can read it, and Secure, so it only travels over an encrypted connection. It is tied to this exact site address, so no other address under jarrettheintz.com can read it or plant one in its place, and it is still sent when you arrive from the link in your email, which is the one journey it exists for. Because it is a cookie, your browser attaches it to requests on its own, and our server does read this one - that is what it is for. Kept: 15 minutes, on this site only. It is deleted as soon as the sign-in completes, and also when the link is refused as expired or already used, as not matching this browser, or because this browser is already signed in. Asking for another link replaces it with a new value.
Cookie: lti-nonce. A single-use random value, set only if you arrive from a school or university learning platform through an LTI launch. It exists so the answer coming back from that platform can be matched to the request we sent, which is how a launch is stopped from being replayed. It holds nothing about you: it is a random string, and the launch route deletes it the moment it has been checked. Scoped to /api/lti, so your browser sends it only to the launch routes and to nothing else on this site. Because it is a cookie, your browser attaches it to requests on its own, and our server does read this one - that is what it is for. Kept: 10 minutes, or until the launch completes, whichever is sooner.
Browser storage: __unlms_preview_owner. Not a learner record at all, and it is listed because the list is the complete list. When an operator previews an unpublished draft of a course in the admin tool, the preview runs on a separate preview origin so the draft behaves the way it will for a learner. Before the draft loads, that origin is wiped, and this key is written afterwards to record which draft the wiped-and-refilled storage now belongs to. It holds the course slug and the build it was previewed from, and nothing about any person: no name, no email, no account, no progress. It is written on the preview origin and not on this site, so if you are reading this as a learner you will not find it in your own developer tools on this domain. It is never sent to this site, and there is no route here that could receive it. Kept: Until a different draft, or the same draft rebuilt, is previewed in that browser, at which point everything on the preview origin is deleted and this is written again.
Your learning record is the one worth having a copy of. You can download the whole thing as a file from any course page, including the one you see after access ends; your courses are at /library.
Two consequences worth stating plainly. Clearing your browser data deletes it, and we cannot restore it, because we never had it. And a different browser or a different machine starts empty for the same reason.
A privacy page is only worth reading for this part, because the storing half is easy to write. None of the following exists anywhere in our systems:
Your progress, completions, scores, watch time, playback position, or how often you open a course. None of it reaches our servers: the entitlement store has a check that fails the build if a column named for any of it appears, so there is nowhere for it to land. It is not gone, it is yours - your browser holds it, and the section above says how to take a copy.
A stored record of the practice questions you answer. This one is narrower than the claim above it, deliberately: the answer itself does pass through this site on its way to the LearnQuiz service that marks it, and the list of stored records above says exactly what goes with it and what does not. Nothing here writes it down, and no page or route here can show it back to you or to anyone else.
Card numbers, expiry dates, or security codes. Payment happens on Stripe pages; card details never reach this site.
Your IP address in readable form. The seat-sharing check turns it into a one-way token immediately and keeps no copy of the address. Signing in reads it too, for the sign-in rate-limit counters: asking for a sign-in link is counted, up to 10 requests from one network address in 15 minutes, and so is opening one, up to 60 from one network address in 15 minutes, while a third counter allows 3 link requests for one email address in an hour. Each counter holds a count and nothing else, is named by a one-way code made from the address rather than the address itself, and expires at the end of its own window. That code is made under a key only our server holds whenever the site has one configured. If it has none, the code is made under a fixed name written in the site code instead, and then anyone who could read the counters could check a guessed network address or email address against them. If you connect Google Calendar, the calendar routes count by network address the same way, always under a key only our server holds, one minute at a time.
Advertising or cross-site tracking identifiers. There are none, from us or from anyone else.
Anything sold, rented, or shared with a data broker. We do not sell your data, and there is no arrangement under which we could.
Authentication is handled by Supabase. Signing in sets a session cookie for this site so you are not asked again on every page. It is a functional cookie, not a tracking one, and it is not shared with anyone. It is named in the list above, with its exact key, what it holds and how long it lasts, because a cookie described only in prose is one you cannot go and check.
Signing out ends the session. Closing the browser does not, which is why a shared computer is worth signing out of.
Checkout hands you to Stripe. You enter card details on their page, under their privacy policy and their security certification, and they tell us the result through a signed webhook.
What we learn from that webhook is that a payment succeeded, was refunded in full or in part, or was disputed, plus the reference that links it to your access. What each of those does to your access is set out at /refunds.
This business sells seats, so it has a legitimate interest in knowing whether one seat is being used by a room of people. The check asks how many separate places a single seat was opened from inside a 30-minute window.
The line is simple and it is enforced in the code, not just stated here: a record about the credential is allowed, and a record about the person is not. "This seat was opened from four places at once" is a fact about a licence. "They watched forty minutes of module three" is a fact about a learner, and nothing here collects it.
This build counts page views through Vercel Analytics: how many people, which pages, nothing that identifies you. The script is served from this domain and its events post back to this domain, so you can confirm it in your browser network panel. It sets no cookie and builds no profile.
Vercel hosts this site, so the counter is served from this domain rather than a third-party one. The most it can ever record is a page view. It cannot see who you are, and it is not joined to your account.
The /data page at /data carries the full vendor-by-vendor account, including what each one can and cannot see.
Write to connect@jarrettheintz.com to see what we hold about you, correct it, export it, or have it deleted. A person answers, and we do not ask you to prove a reason.
Deleting your account removes your sign-in record and your access rows. Two kinds of payment record outlive it, and both are in the list above. Ours is the event ledger that stops a replayed message from Stripe granting a second copy of what you bought. It keeps five things about each message and nothing else: the Stripe event id, the kind of event, the status of our handling of it, the reason the last attempt did not succeed if one did not, and when it was processed. It holds no card details and no name, is not linked to your account, and stays indefinitely, because an event id we had forgotten is one we would process again. Theirs is whatever Stripe is legally required to keep, which stays with Stripe under their policy.
Depending on where you live you may have statutory rights under the GDPR, the UK GDPR, or a US state privacy law. We honour those requests from anyone who asks, wherever they live, because running two standards would be worse than running one.
These are professional and higher-education courses and they are not directed at children. We do not knowingly create accounts for anyone under 16. If one exists, tell us at connect@jarrettheintz.com and it will be removed.
If what we collect changes, this page changes first. That is the order, and it is the one rule on this page we have previously broken and had to repair.
The terms of use are at /terms, the refund policy at /refunds, and the longer plain-language account of the same practices at /data.
Terms of useRefunds and disputesThird-party licencesCopyright and IPconnect@jarrettheintz.com