Is Speechify — Text to Speech safe?
Speechify is medium risk. Entering Speechify sidepanel voice chat requests mic access, gets a short-lived OpenAI Realtime token via Speechify, opens a WebSocket to Realtime, and sends PCM16 mic frames as input_audio_buffer.append. Testing didn't reach this feature.…
Who publishes itSpeechify Inc - no other listings under this identity, 1 shared hostname
Speechify Inc - no other listings under this identity, 1 shared hostname
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 1 hostname
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Voice chat streams microphone audio to OpenAI
Entering Speechify sidepanel voice chat requests mic access, gets a short-lived OpenAI Realtime token via Speechify, opens a WebSocket to Realtime, and sends PCM16 mic frames as input_audio_buffer.append.
Testing didn't reach this feature.
You open the Speechify sidepanel in voice-chat mode.
The sidepanel captures microphone audio, gets a short-lived OpenAI token through Speechify, and streams audio frames to OpenAI Realtime.
| Field | Value | Why it matters | |
|---|---|---|---|
Microphone audio | PCM16 audio frame from a microphone stream | Your spoken voice is converted into audio frames for the realtime chat session. | |
Realtime model | gpt-realtime-mini-2025-12-15 | The connection names the realtime model used for the voice session. | |
Ephemeral token | openai-insecure-api-key.redacted-session-token | A short-lived token authorizes the OpenAI WebSocket session for the voice chat. | |
Page context | Page Title: Product roadmap notes; Page URL: https://docs.google.com/document/d/abc123 | The sidepanel chat can include the title, URL, and visible page text from the page you are asking about. |
| Content-Type | application/json |
| Authorization | Bearer <redacted> |
| X-Speechify-Client | DesktopExtension |
| Sec-WebSocket-Protocol | realtime, openai-insecure-api-key.<redacted> |
Voice-mode microphone capture and OpenAI Realtime streaming
let stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
setStream(stream);
trackEvent("Voice Mode Clicked", { flow: "realtime_voice_chat" });let body = JSON.stringify({
session: {
type: "realtime",
model: config.model || defaultModel,
output_modalities: ["audio"],
audio: { output: { voice, speed: config.speed || 1.3 } },
tools: config.tools || [],
instructions: config.instructions
}
});
let response = await fetch(`${ln.voiceTyping.url}/openai-ephemeral-token-v2`, {
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "X-Speechify-Client": "DesktopExtension" },
method: "POST",
body
});async connect() {
let tokenResponse = await this.getOpenAIEphemeralToken();
let token = tokenResponse?.value || "";
let url = `wss://api.openai.com/v1/realtime?model=${this.config.model}`;
this.ws = new WebSocket(url, ["realtime", `openai-insecure-api-key.${token}`]);
this.ws.onmessage = event => { this.handleMessage(event.data); };
}async sendAudio(buffer) {
if (!this.isConnected || !this.ws) throw new Error("Not connected to Realtime API");
let event = { type: "input_audio_buffer.append", audio: this.arrayBufferToBase64(buffer) };
this.send(event);
}- ce-voice-typing.speechify.com
Speechify endpoint that issues the short-lived OpenAI Realtime token.
- api.openai.com
OpenAI Realtime WebSocket endpoint that receives microphone audio frames.
Meeting transcripts sent for AI summaries
After Meeting Capture stops, offscreen script filters transcript lines, formats them with speaker labels, and posts them to Speechify AI summarization.
Verified fields: model, transcripts, transcriptContent, stream.
Testing stopped early.
You stop a Meeting Capture session that has final transcript lines.
The extension sends the full final transcript and speaker-labeled transcript text to Speechify AI summarization.
| Field | Value | Why it matters | |
|---|---|---|---|
AI model | gpt-4.1-mini | The request names the model the backend should use for the meeting summary. | |
Transcript objects | {source: "tab", speakerName: "Other Speaker", text: "Let us review the launch plan."} | Each final transcript entry can include speaker names, whether the source was tab or microphone audio, timestamps, and the spoken text. | |
Full transcript text | [Other Speaker]: Let us review the launch plan. | The request also includes a single combined transcript, making the whole recorded meeting available for summarization. | |
Streaming flag | stream: true | The endpoint is asked to stream the generated summary back as chunks. |
| Content-Type | application/json |
| Authorization | Bearer <redacted> |
| X-Speechify-Client | DesktopExtension |
Transcript formatting and AI-summary POST
if (this.callbacks.onSummarizationRequest) {
this.callbacks.onSummarizationRequest({
transcripts: this.transcripts,
startTime: this.startTime,
endTime: new Date().getTime()
});
this.startTime = null;
}oA = transcripts => {
let finalRows = transcripts.filter(row => row.text.trim().length > 0 && row.isFinal);
return finalRows.length === 0 ? "" : finalRows.map(row => `[${row.speakerName || (row.source === "tab" ? "Other Speaker" : "User")}]: ${row.text}`).join("\n\n");
};let response = await fetch(`${ke.voiceTyping.url}/ai-scribe-summarize`, {
method: "POST",
body: JSON.stringify({
model: "gpt-4.1-mini",
transcripts,
transcriptContent,
stream: true
}),
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "X-Speechify-Client": "DesktopExtension" },
signal: abortController.signal
});- ce-voice-typing.speechify.com
Speechify AI summarization endpoint that receives full meeting transcript content.
Meeting transcripts can be emailed through Speechify
Using Meeting Capture email sharing, the sidepanel collects recipient emails, sent to an offscreen route that posts the meeting name, summary, transcript, sender, and share link to Speechify per recipient.
Testing didn't reach this step.
You choose the meeting email-share feature and enter recipient email addresses.
The extension posts the meeting name, generated summary, full formatted transcript, sender name, and optional share link through Speechify app backend.
| Field | Value | Why it matters | |
|---|---|---|---|
Recipient email | manager@example.com | The address you enter determines who receives the meeting email. | |
Sender name | Alex Reader | The request can include the display name or email from the signed-in Speechify account. | |
Meeting name | Weekly roadmap sync | The email identifies the captured meeting by its tab title. | |
Generated summary | The team reviewed launch dates and assigned follow-up work. | The generated meeting summary is included in the email request. | |
Formatted transcript | [{type:"left",content:"Let us review the roadmap."},{type:"right",content:"I can take the follow-up."}] | The full transcript is converted into left and right entries and included with the email request. | |
Share link | https://app.speechify.com/item/meeting-note-123?folder=abc | If a saved library item exists, the request can include a share URL for that item. |
| Content-Type | application/json |
| Authorization | Bearer <redacted> |
Email-share UI route and backend POST
const send = async () => {
onChangeSendingEmail(true);
onClose();
let result = await hc("meeting-capture/send-email", { to: recipients });
if (result?.result?.success) {
setEmailRecipientsCount(recipients.length);
setShowEmailSentToast(true);
}
onChangeSendingEmail(false);
};let failures = (await Promise.allSettled(recipients.map(to => fetch(`${ke.app.url}/api/meeting/email`, {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({
to,
senderName,
meetingName,
summary,
transcripts,
...(shareUrl ? { shareUrl } : {})
})
})))).filter(result => result.status === "rejected");- app.speechify.com
Speechify app endpoint that receives meeting email-share requests.
+1 more finding not shown
What it can do
Permissions this extension asks for, as declared in version 14.8.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 14.7.0, which we have not unpacked yet.
Read and change your data on every site you visit
<all_urls> and 3 more
Run hidden pages in the background
offscreen
See the address and title of every tab you have open
tabs
Capture the video and audio of a tab
tabCapture
Add items to the right-click menu
contextMenus
Store data in your browser
storage
Schedule its own background tasks
alarms
Run its own code inside the pages you visit
scripting
Store an unlimited amount of data in your browser
unlimitedStorage
Read information about your CPU
system.cpu
Read how much memory your computer has
system.memory
Show a panel beside the page
sidePanel