Privacy
Your camera video is processed on your device and never uploaded. No accounts, no cookies.
Your camera stays on your device
FingerSprint asks for the camera only after you press Turn on camera. Each frame goes to a hand tracker (Google’s MediaPipe, running inside your browser) and to FingerSprint’s letter classifier, which also runs in your browser. Video frames, images and hand landmarks are never sent over the network, never written to storage and never put in a URL.
- The lighting check copies a 32×24-pixel thumbnail of the video into memory every half second to measure brightness, then discards it.
- Results such as letters per minute are computed from the letters you lock in, not from stored video.
- Hand tracking runs only while a sprint or practice run is starting or under way. On the results page the camera stays on so Run it back starts at once, but no frames are tracked or read.
- The camera turns off when you go back to the start page or close the tab.
- For troubleshooting J and Z, the last six seconds of hand landmarks stay in memory during a run. Only if you open the debug panel (Shift+D) and press Save last attempt does your browser save them as a file on your device; that file is never uploaded.
Words beta
Words mode checks whole signs, one at a time. It runs entirely in your browser, like the letters, but tracks more: up to two hands and your upper body, including the 11 face points of Google’s pose model (eyes, ears, nose and the corners of the mouth), so it can tell a sign at the chin from one at the forehead. No face mesh is used.
- The landmarks of each attempt are read by a small sign model on your device and then discarded. They are never sent, stored or put in a URL.
- Words loads two more files when you press Start words, never before: the body model,
pose_landmarker_lite.task, fromstorage.googleapis.com, and FingerSprint’s own sign model from this site. Opening a sign’s sheet loads only its replay, also from this site. - The replays on each sign’s sheet are landmark drawings made from public recordings by Deaf signers (PopSign ASL, CC BY 4.0), not video of anyone.
- On your device Words keeps only counts per sign (tries, confirmed, skipped) and a short history of rounds; see below.
Downloads from other services
To track your hand, your browser downloads files from two content delivery services:
- the MediaPipe hand-tracking runtime and its WebAssembly engine from
cdn.jsdelivr.net(jsDelivr); - the hand model,
hand_landmarker.task, fromstorage.googleapis.com(Google Cloud Storage); - in Words beta only, the body model,
pose_landmarker_lite.task, from the samestorage.googleapis.com.
These downloads start when you press Start, or a little earlier so the sprint starts faster: when you point at a Start button with a mouse or move keyboard focus to it, or after about three seconds on a computer with a mouse or trackpad and a window at least 768 pixels wide that is not in data-saver mode. Touching a Start button only downloads them as part of starting. Before you press Start, the files are only downloaded, never run. Like any web request, a download tells the service your IP address and browser version, under that service’s own privacy policy. Nothing about you or your camera is added to these requests.
Usage counts
We also send the same fixed events below to Twilio Segment, configured for forwarding to Google Analytics. These events use a random identifier held only in the open page, with a new session after 30 minutes of inactivity. We do not send your name, email, form contents, camera data, typed words, query strings or URL fragments. Segment receives your IP address as part of the network connection; our event payload uses a zeroed IP address. We do not enable advertising integrations or session recordings.
This preference applies to this site and is saved in your browser. Browser privacy signals always take priority.
FingerSprint uses PostHog (us.i.posthog.com, United States) in cookieless mode to count how the
app is used. It sets no cookies, keeps no person profiles, records no sessions or screens, captures no clicks or
typing automatically, and honors Do Not Track, Global Privacy Control and the preference below. Page addresses are sent without their query string or
fragment. PostHog receives your IP address and browser user agent with each request, as any web server does,
and may derive an approximate location from them; in cookieless mode it combines them with a salt that rotates
daily to count unique visitors without storing an identifier on your device.
Every event and property FingerSprint sends is listed below. Each property is a yes/no value, a fixed label or a numeric range, never a raw score, word, name, image or landmark.
| Event | Properties and values | When |
|---|---|---|
$pageview | catalog page address only; no query or fragment | opened page; Segment Page maps to page_view |
welcome_viewed | returning: yes / no; entry_point: landing | First landing hydration per page, returning flag from existing local activity computation. |
camera_result | result: granted, denied, in_use, not_found, overconstrained, insecure, other; reason: granted, denied, in_use, not_found, overconstrained, insecure, other | Current acquisition resolves or fails; cancelled/stale acquisition emits no outcome. |
tracker_ready | delegate: gpu, cpu; load_bucket: 0, 1, 2-4, 5-9, 10-19, 20+; stage: runtime, wasm, tracker, pose, model, timeout; backend: gpu, compatibility; latency_bucket: lt_1s, 1_3s, 3_10s, 10_30s, ge_30s | Actual loader resolves. Retained ready fields now also cover Words. |
tracker_failed | stage: runtime, wasm, tracker, pose, model, timeout; backend: gpu, compatibility; latency_bucket: lt_1s, 1_3s, 3_10s, 10_30s, ge_30s; error_code: load_failed, timeout | Loader rejects once. No generic failure for same rejection. |
warmup_completed | secs_bucket: 0-1, 2-4, 5-9, 10-19, 20+; duration_bucket: lt_10s, 10_30s, 30_60s, 1_5m, 5_15m, 15_60m, ge_60m | Observed successful hold only; skip is exited. |
core_action_started | mode: sprint, zen; tier: beginner, standard, hard, custom | Engine actually starts after countdown. |
core_action_completed | mode: sprint, zen; tier: beginner, standard, hard, custom; lpm_bucket: 0, 1-9, 10-19, 20-29, 30-39, 40-49, 50-59, 60-79, 80-99, 100+; acc_bucket: none, 0-49, 50-69, 70-79, 80-89, 90-94, 95-100; clean_bucket: none, 0-49, 50-69, 70-79, 80-89, 90-94, 95-100; is_pb: yes / no; sprint_n_bucket: 1, 2, 3-5, 6-10, 11-25, 26-50, 51+ | Engine ends and existing aggregate run recording returns; no interrupted sprint success. |
daily_active | days_since_first_bucket: 0, 1, 2-6, 7-13, 14-29, 30-59, 60+ | Existing first local-day activity; same fs.v1 state and buckets. |
retention_milestone | milestone: d1, d7, d30 | Existing locally deduplicated day-1, day-7 or day-30 window; no persistent remote identity. |
share_clicked | surface: results, landing; channel: native, download, copy; method: native, download, copy | Accepted share/copy/download attempt before async result. Previously native emitted after success; corrected in schema v2 without alias emission. |
pro_door_clicked | surface: results; price_variant: 24; practice_mode: sprint, zen, words, unknown | Existing Pro fake door click; no payment or purchase claim. |
teacher_interest_clicked | surface: landing, results; practice_mode: sprint, zen, words, unknown | Existing For teachers contact link click; intent only. |
words_round_started | count_bucket: 1, 2-5, 6-10, 11+ | Nonempty round engine and loop start. |
words_round_completed | confirmed_bucket: 0, 1, 2-5, 6-10, 11+; attempts_bucket: 0, 1, 2-5, 6-10, 11-20, 21-50, 51+; skipped_bucket: 0, 1, 2-5, 6+ | Existing round end callback, including explicit early finish; historical semantics retained. Not proof all signs confirmed. |
words_attempt | result: pass, couldnt_confirm; reason: ok, partner, other_sign, none, none_margin, no_hand, no_pose, hand_lost, too_short, too_long | Current round receives one verifier result; existing stale-result guards in wordsLoop. |
contact_submitted | topic: question, waitlist, teacher, privacy, other; result: received | HTTP 201 and body.ok true, honeypot empty; server receipt, not email delivery. |
camera_requested | trigger: enable, retry; practice_mode: sprint, zen, words, unknown | New actual camera acquisition after accepted Enable or Retry; no event for cached stream or in-flight duplicate. |
tracker_started | stage: runtime, wasm, tracker, pose, model, timeout; backend: gpu, compatibility | New loader attempt, after cached/in-flight guard. |
warmup_started | none | Enter first-session hold warmup. |
warmup_exited | duration_bucket: lt_10s, 10_30s, 30_60s, 1_5m, 5_15m, 15_60m, ge_60m; exit_reason: skip, back | Explicit skip or Back during warmup. |
practice_configuration_changed | mode: sprint, zen, words, unknown; tier: beginner, standard, hard, custom; handedness: left, right; strict_mode: yes / no | Accepted practice launch or changed tier/handedness/strict preference; no initial-load event. |
practice_interrupted | reason: hidden_tab, camera_ended, user_stop; progress_bucket: not_started, lt_25, 25_49, 50_74, 75_99, complete, unknown | Observed hidden tab, camera ended or explicit stop. Zen/Words hidden means pause, not abandonment. Progress unknown for untimed practice. |
recognition_issue_summary | issue_type: unconfirmed_attempt, near_miss; frequency_bucket: 0, 1, 2_5, 6_20, 21_100, 101_plus | At next practice terminal, at most one summary per open page; count distinct near-miss episodes or rejected Words attempts. Not a frame count. |
custom_words_loaded | input_method: text; word_count_bucket: 0, 1, 2_5, 6_20, 21_100, 101_plus | Validated list accepted by Practice button; count only. |
reference_opened | reference_type: alphabet, sign_guide | Reference sheet mounts, once per opening; navigation within sheet is not another open. |
share_completed | method: native, download, copy | Native browser share promise resolves or clipboard promise succeeds; no download saved event. |
share_failed | method: native, download, copy; error_code: unavailable, clipboard, download | Observed native, clipboard or download dispatch error. AbortError is cancellation, not failure. |
action_failed | operation: contact, runtime; stage: request, response, import; error_code: network, timeout, validation, rate_limit, server, load_failed; recoverable: yes / no | One failure per submitted contact request or engine-runtime import attempt; closed error codes only. MediaPipe tracker failures use tracker_failed instead. |
app_opened | launch_source: direct | First client hydration; direct is generic web load, not attribution proof. |
screen_view | screen_id: welcome, primer, getready, warmup, countdown, drill, results, camError, modelError, inapp, stopped, words; previous_screen_id: welcome, primer, getready, warmup, countdown, drill, results, camError, modelError, inapp, stopped, words | Distinct accepted app phase transition; not a Segment Page call and does not create page_view. |
setting_changed | setting_id: theme, sound, motion, haptics, reference; value: system, light, dark, on, off, reduce, full, auto, always | Explicit harmless preference actually changes; no debug, consent or camera settings. |
help_opened | topic_id: shortcuts; entry_point: practice | User opens shortcut help. |
retry_requested | operation: camera, tracker; attempt_bucket: 0, 1, 2_5, 6_20, 21_100, 101_plus; previous_error_code: denied, in_use, not_found, overconstrained, insecure, other, runtime, wasm, tracker, pose, model, timeout | Explicit Retry or Compatibility control, bounded attempt count and prior classified failure. |
Hosting
The original FingerSprint site is hosted on OpenAI Sites through Cloudflare. The custom-domain edition uses Render. As with any website, the host’s request logs can include your IP address, browser user agent, the page requested and the time, under the host’s own policies. FingerSprint’s own server code does one thing: it stores the messages you send with the contact form (below). It sees nothing else of yours.
If you write to us
The contact form stores your email address, the topic you pick, your message and the time you sent it in FingerSprint’s own database. We use them only to reply to you (for the Pro waitlist: one email when Pro ships). Messages sent on the original OpenAI Sites edition are stored in its Cloudflare database; messages sent on the Render edition are stored in Render PostgreSQL. Existing messages are not automatically copied between hosts.
- We remove messages and addresses older than 12 months when a new message arrives or the owner runs a cleanup. If the form is quiet, older messages can remain until that cleanup. If you ask, we delete yours sooner, within 30 days of your request.
- To ask about your data or have it deleted, write to us with the topic “Privacy or deletion”.
- To limit spam, the form takes about five messages an hour from one connection. To count them it keeps a one-way hash of your IP address, mixed with a random value that is replaced when the first message of a new day arrives. The hash only counts the last hour’s messages and is cleared out as new messages arrive; once the day’s value is replaced, an old hash can no longer be matched to any address. Your IP address itself is never stored, and neither is your browser’s user agent.
- Sending the form also counts one anonymous
contact_submittedevent with the topic only (see the table above).
What is stored on your device
The analytics preference uses project-analytics:disabled. FingerSprint also keeps its app data in your browser’s localStorage, under the key fs.v1. It holds:
version: the format version;settings: theme, sound, vibration, motion, signing hand, reference card, Strict mode and word list (Beginner, Standard or Hard);-
runs: your last 200 sprints and practice runs, each with its date, mode, length, words and letters finished, letters per minute, accuracy, clean rate and best streak; per letter, how many locked, the median time and how many were misread or assisted; up to 50 “asked for this letter, read as that one” counts; and up to 100 two-letter timings (such as “TH”). Never the words themselves; pb: your personal best per word list, in letters per minute;sprintCount: how many sprints you have finished;firstSeenDayandlastActiveDay: the local dates of your first and latest visits;milestonesSent: which retention milestones were already counted;signs(Words beta): for each of the model’s fixed signs you have tried, how many tries, how many were confirmed and how many you skipped;wordRounds(Words beta): your last 100 rounds, each with its date, length in signs, signs confirmed, tries, skips and duration.
It never holds video, images, hand or body landmarks or typed words, and none of it is sent anywhere. Words you type for “Your words” stay in the open page only and are gone when you close or reload it. A share card is drawn in your browser from these numbers, never from the camera. To delete it all, open Settings and choose Clear my data, or clear this site’s data in your browser’s settings.
Ages
FingerSprint is meant for people aged 13 and up.