Is Text to Speech Google Docs safe?
Text to Speech Google Docs sends page hostname and a snippet of the text you read aloud to Amplitude on every TTS session.
Each time you start a text-to-speech session, the extension transmits an analytics event to Amplitude that includes the site hostname, the first 32 characters of the text being read, the detected language, voice settings, and a stable anonymous user ID persisted across sessions. A secondary event also sends a normalised URL path pattern. Both identifiers — an anonymousUserId UUID and a device ID stored in chrome.storage.local — are created once and never rotated, enabling cross-session tracking.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
TTS Sessions Send Text Excerpts to Amplitude
Starting a text-to-speech session builds an Amplitude event with the page host, first 32 characters of the first text chunk, voice/rate settings, language/length metadata, and persistent anonymous user/device IDs.
Request fired, body empty.
What actually happens
You start text-to-speech on the current page.
The extension prepares an analytics event that includes where the reading happened and a short excerpt of the text.
Fields built for the Amplitude session event
- Page hostdocs.google.com (illustrative)
Shows which website was open when you started the reading session.
- Text excerptQuarterly planning notes for (illustrative)
Includes the first 32 characters of the first text chunk read by the extension.
- Anonymous user ID550e8400-e29b-41d4-a716-446655440000 (illustrative)
Lets separate reading sessions be tied to the same browser profile over time.
- Device IDdev_1720818929123_q9x4m2ka (illustrative)
Adds another stable identifier for the browser where the extension is installed.
- Voice and reading settingsvoice_name: Google US English, rate: 1, text_lang_detected: en, text_length: 1842 (illustrative)
Describes how the reading session was configured.
- Normalized URL path/document/d/{ID}/edit (illustrative)
For extraction-result analytics, this records a generalized page path pattern instead of the full path.
Captured request
200 OK observed during dynamic analysis; recorded request and response bodies were empty.
- Content-Type
- application/json
The code that does this
Session event construction and Amplitude submission
Readable session-start fields
assets/player-CmA0CLvy.jsconst s = (() => { try { return t.trackUrl ? new URL(t.trackUrl).host : "" } catch { return "" } })(), m = t.source ?? X(s), k = await ht(), N = Date.now(), y = k !== null && N - k < 6e4, M = k === null ? 0 : Math.floor((N - k) / (1e3 * 60 * 60 * 24)), Y = t.paragraphs.reduce((z, ct) => z + ct.text.length, 0), F = (((i = C[0]) == null ? void 0 : i.text) ?? "").slice(0, 32);t.textFirstChars = F, I(v.SESSION_START, { [o.SOURCE]: m, [o.HOST]: s, [o.VOICE_NAME]: t.settings.voiceName ?? "", [o.VOICE_LANG]: t.resolvedVoiceLang, [o.RATE]: t.settings.rate, [o.TEXT_LENGTH]: Y, [o.TEXT_LANG_DETECTED]: t.pageLang ?? "", [o.VOICE_LANG_MATCHES_PAGE]: J(t.resolvedVoiceLang, t.pageLang), [o.TEXT_FIRST_CHARS]: F, [o.HOUR_OF_DAY]: new Date().getHours(), [o.IS_CONTINUATION]: y, [o.DAYS_SINCE_LAST_SESSION]: M}), (async () => await pt({ sessionId: await ft(), startedAt: t.startedAt, lastActivityAt: Date.now(), source: m, host: s, atPct: 0}))();Readable Amplitude POST sender
assets/sentry-CSvuoUnh.jsconst Ni = "https://api2.amplitude.com/2/httpapi";async function Ai(e, t, n) { var a; const [r, s, o] = await Promise.all([yi(), pn(), Ii()]), i = typeof chrome < "u" && ((a = chrome.runtime) != null && a.getManifest) ? chrome.runtime.getManifest().version : "unknown"; return { user_id: r, device_id: s, session_id: (n == null ? void 0 : n.sessionId) ?? o, time: (n == null ? void 0 : n.time) ?? Date.now(), event_type: e, app_version: i, platform: "Chrome Extension", event_properties: { ...t, product: "tts-gd" } }}async function ua(e, t = {}, n) { const r = "8f4716e33e96712ef837134a2d3f25b8"; try { const s = await Ai(e, t, n), o = await fetch(Ni, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ api_key: r, events: [s] }), keepalive: !0 }); o.ok || console.warn("[TTS GD] analytics track non-2xx", e, o.status) } catch (s) { console.warn("[TTS GD] analytics track failed", e, s) }}Readable persistent analytics identifiers
assets/sentry-CSvuoUnh.jsconst se = "anonymousUserId";async function yi() { const e = await chrome.storage.local.get([se]); if (typeof e[se] == "string") return e[se]; const t = crypto.randomUUID(); return await chrome.storage.local.set({ [se]: t }), t}const k = "ttsgd_amplitude_device_id", kt = "ttsgd_amplitude_session_id", oe = "ttsgd_unique_voices_tried", $e = "ttsgd_last_session_end_at", He = "ttsgd_install_action_tracker", pe = "ttsgd_hanging_session";function Ie() { try { return typeof localStorage < "u" } catch { return !1 }}function Ti() { const e = Math.random().toString(36).slice(2, 10); return `dev_${Date.now()}_${e}`}async function pn() { if (Ie()) { const n = localStorage.getItem(k); if (n) return n } const e = await chrome.storage.local.get([k]); if (typeof e[k] == "string") { const n = e[k]; return Ie() && localStorage.setItem(k, n), n } const t = Ti(); return await chrome.storage.local.set({ [k]: t }), Ie() && localStorage.setItem(k, t), t}External analytics destinations used by this code path
- api2.amplitude.com
Receives the text-to-speech session event through the /2/httpapi endpoint.
Where it sends data
Destinations our analysis observed Text to Speech Google Docs contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- api2.amplitude.comwidely used
Text to Speech Google Docs sends data to api2.amplitude.com. A widely used service: 42 other extensions we have analyzed send data here.