FingerSprint Back to the sprint

Privacy

Your camera video is processed on your device and never uploaded. No accounts, no cookies.

Last updated 5 October 2026

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.

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.

Downloads from other services

To track your hand, your browser downloads files from two content delivery services:

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.

EventProperties and valuesWhen
$pageviewcatalog page address only; no query or fragmentopened page; Segment Page maps to page_view
welcome_viewedreturning: yes / no; entry_point: landingFirst landing hydration per page, returning flag from existing local activity computation.
camera_resultresult: granted, denied, in_use, not_found, overconstrained, insecure, other; reason: granted, denied, in_use, not_found, overconstrained, insecure, otherCurrent acquisition resolves or fails; cancelled/stale acquisition emits no outcome.
tracker_readydelegate: 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_30sActual loader resolves. Retained ready fields now also cover Words.
tracker_failedstage: 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, timeoutLoader rejects once. No generic failure for same rejection.
warmup_completedsecs_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_60mObserved successful hold only; skip is exited.
core_action_startedmode: sprint, zen; tier: beginner, standard, hard, customEngine actually starts after countdown.
core_action_completedmode: 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_activedays_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_milestonemilestone: d1, d7, d30Existing locally deduplicated day-1, day-7 or day-30 window; no persistent remote identity.
share_clickedsurface: results, landing; channel: native, download, copy; method: native, download, copyAccepted share/copy/download attempt before async result. Previously native emitted after success; corrected in schema v2 without alias emission.
pro_door_clickedsurface: results; price_variant: 24; practice_mode: sprint, zen, words, unknownExisting Pro fake door click; no payment or purchase claim.
teacher_interest_clickedsurface: landing, results; practice_mode: sprint, zen, words, unknownExisting For teachers contact link click; intent only.
words_round_startedcount_bucket: 1, 2-5, 6-10, 11+Nonempty round engine and loop start.
words_round_completedconfirmed_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_attemptresult: pass, couldnt_confirm; reason: ok, partner, other_sign, none, none_margin, no_hand, no_pose, hand_lost, too_short, too_longCurrent round receives one verifier result; existing stale-result guards in wordsLoop.
contact_submittedtopic: question, waitlist, teacher, privacy, other; result: receivedHTTP 201 and body.ok true, honeypot empty; server receipt, not email delivery.
camera_requestedtrigger: enable, retry; practice_mode: sprint, zen, words, unknownNew actual camera acquisition after accepted Enable or Retry; no event for cached stream or in-flight duplicate.
tracker_startedstage: runtime, wasm, tracker, pose, model, timeout; backend: gpu, compatibilityNew loader attempt, after cached/in-flight guard.
warmup_startednoneEnter first-session hold warmup.
warmup_exitedduration_bucket: lt_10s, 10_30s, 30_60s, 1_5m, 5_15m, 15_60m, ge_60m; exit_reason: skip, backExplicit skip or Back during warmup.
practice_configuration_changedmode: sprint, zen, words, unknown; tier: beginner, standard, hard, custom; handedness: left, right; strict_mode: yes / noAccepted practice launch or changed tier/handedness/strict preference; no initial-load event.
practice_interruptedreason: hidden_tab, camera_ended, user_stop; progress_bucket: not_started, lt_25, 25_49, 50_74, 75_99, complete, unknownObserved hidden tab, camera ended or explicit stop. Zen/Words hidden means pause, not abandonment. Progress unknown for untimed practice.
recognition_issue_summaryissue_type: unconfirmed_attempt, near_miss; frequency_bucket: 0, 1, 2_5, 6_20, 21_100, 101_plusAt 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_loadedinput_method: text; word_count_bucket: 0, 1, 2_5, 6_20, 21_100, 101_plusValidated list accepted by Practice button; count only.
reference_openedreference_type: alphabet, sign_guideReference sheet mounts, once per opening; navigation within sheet is not another open.
share_completedmethod: native, download, copyNative browser share promise resolves or clipboard promise succeeds; no download saved event.
share_failedmethod: native, download, copy; error_code: unavailable, clipboard, downloadObserved native, clipboard or download dispatch error. AbortError is cancellation, not failure.
action_failedoperation: contact, runtime; stage: request, response, import; error_code: network, timeout, validation, rate_limit, server, load_failed; recoverable: yes / noOne failure per submitted contact request or engine-runtime import attempt; closed error codes only. MediaPipe tracker failures use tracker_failed instead.
app_openedlaunch_source: directFirst client hydration; direct is generic web load, not attribution proof.
screen_viewscreen_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, wordsDistinct accepted app phase transition; not a Segment Page call and does not create page_view.
setting_changedsetting_id: theme, sound, motion, haptics, reference; value: system, light, dark, on, off, reduce, full, auto, alwaysExplicit harmless preference actually changes; no debug, consent or camera settings.
help_openedtopic_id: shortcuts; entry_point: practiceUser opens shortcut help.
retry_requestedoperation: 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, timeoutExplicit 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.

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:

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.