Blog

What Voicecape sends over the network

Three requests, all named and verifiable from outside the app: a licence activation, a background re-check of it at most weekly, and a daily update check.

Every voice product says it respects your privacy, and on its own the sentence is worth nothing, because it is compatible with almost any implementation underneath. A product that streams your microphone to a server can say it. So can one that never opens a socket.

The version of the claim that carries weight is the one that names the requests. Voicecape makes three: one because you pressed a button, one that renews that same licence in the background at most once a week, and one on a daily timer. Each is below with the host, the method, the fields and the reason each field exists, followed by the list of things that are never sent, an honest note about the single path that could produce traffic nobody advertises, and two ways to check all of it without trusting any of it.

The activation request

When you buy Voicecape you receive a licence key. You paste it into Settings and press Activate, and that press is the entire trigger for a POST to https://api.voicecape.com/v1/activate. It does not happen at launch and it does not happen on a schedule. The body carries three fields and nothing else.

  • license_key — the key you just pasted. Without it there is nothing for the server to check.
  • device_id — an identifier for this installation, generated by the app the first time one is needed rather than read from the hardware. It is not a serial number, not a MAC address and not anything Apple issued. It is stored locally and reused, which is what stops a reinstall from burning another of your activation slots.
  • device_name — a label for the activation. The current build sends a fixed string rather than the name you gave your Mac, so activations show up under a generic label; the field exists so that a licence with several machines on it can be a list you can tell apart.

The email address on that same settings screen is stored on the Mac and used locally. It is not one of the three fields, so it does not travel with this request.

The reason a device identifier exists

One licence activates up to three Macs, and that is why the second and third fields are in the body. A limit you cannot count is not a limit, so the licence service has to tell one activation from another, which requires something stable per machine. The label is what makes the count usable from your side: when a fourth Mac is refused you have to decide which one to release, and rows you cannot distinguish do not help you do that.

The response comes back signed, and the app verifies that signature against a public key compiled into the application before storing anything at all, so a forged reply from something sitting in the middle of the connection is rejected rather than believed. The purchase itself is not an app request — the checkout button opens your browser at the payment page, and everything after that is between your browser and the payment provider.

The revalidation request

Activation is not the only request that carries your licence key, and an earlier version of this page said that it was. On a Mac where the licence is already active, the app re-checks it in the background when you open the app — a POST to https://api.voicecape.com/v1/validate carrying the same licence key, the same device_id, and the activation_id the server returned the first time. Nothing else is in the body, and no build that has never been activated makes this request at all.

What holds it back is a stored timestamp rather than a timer: the call only goes out when seven days have passed since the last successful one, so opening the app four times in an afternoon produces no traffic. It is not waited on either — a slow or unreachable server does not delay the first screen, because an application whose promise is that it works offline must not sit blank while a network call decides.

Failure is deliberately silent. The activation response is signed and carries an expiry ninety days out, the app verifies that signature against a key compiled into the binary before storing anything, and it keeps working on that stored permission afterwards. A refused or unreachable revalidation therefore changes nothing you can see. The asymmetry is the point: an outage on our side must not lock an application somebody has already paid for, and the only thing that does lock it is ninety days passing with no successful check at all.

The update check

The third request is the update check, run by Sparkle, the standard updater for Mac applications distributed outside the Mac App Store. Its settings sit in the app’s own Info.plist, inside the bundle, where you can read them on your own machine: the feed is https://voicecape.com/appcast.xml, automatic checks are on, and the interval is 86400 seconds.

Once a day, then, the app fetches a static XML file over HTTPS. Sparkle identifies the application and the version currently running, because comparing what you have against what is published is the entire purpose of the request. There is no account in it, no licence key and no device identifier.

When the feed does contain something newer, Sparkle downloads it in the background from the host named in that feed entry, and then stops. Automatic installation is switched off in the same file, with the reason written next to the setting: this is an application people hold down mid-sentence, and swapping it out underneath somebody is not acceptable. You get asked, and until you agree nothing is replaced. That file also carries a public signing key, and Sparkle verifies every download against it before running anything, so an update served from a hijacked host or a rewritten DNS record fails verification instead of executing.

The negative list

A list of things a program does not do is only worth reading if each item is the kind of claim you could catch being false.

  • No audio, ever. Recognition runs on the Mac against a speech model that ships inside the app bundle, the captured audio is processed in memory and discarded, and the application has no endpoint to send it to.
  • No transcript, ever — neither the raw recognition nor the cleaned result. Cleanup is rule-based code running locally rather than a call to a language model, which is the difference between a feature that could quietly need a network and one that cannot.
  • No personal dictionary, no settings, no dictation history. All of it lives in a local store on your Mac.
  • No analytics SDK. The application’s dependency list has a single third-party entry, and it is the package used to call the app’s own local core.
  • No crash reporter. Nothing collects a stack trace and posts it anywhere.
  • No telemetry endpoint, and no way to introduce one by configuration — the settings store refuses keys it does not recognise instead of storing them, and a test holds that behaviour in place.
  • No model download on a normal install, because the model is already in the app bundle. That is also why a first run has no download step, and why dictation works before the machine has ever been online.

The one exception

There is a path that could produce model traffic. Stating a claim as absolute when it has an exception is how the claim gets destroyed later by whoever finds the exception, so here it is.

The app resolves the speech model in a fixed order: the app bundle first, then a models folder in your home directory. The bundle is checked first deliberately, so a half-finished download can never shadow a working model. On any intact install the bundled file resolves, that is the file opened, and the download step is skipped entirely rather than shown to you already complete.

If the bundled file cannot be resolved, the app treats the model as missing and offers to fetch it, and that fetch goes to Hugging Face, where the whisper.cpp authors publish the model files. It is a recovery path for a damaged install, and it takes a button press.

So the accurate claim is not that model traffic is impossible. It is that none happens on a working install, and the only route that would produce any is a repair you start yourself. That cuts the other way too: if you ever see this application reach for Hugging Face, it means your install is damaged, not that the software changed its mind about where recognition happens.

Two checks you can run yourself

Neither of these requires special tooling, and neither requires believing anything above.

  1. Turn on airplane mode and use the app normally. Dictate in a few applications, switch languages, add a dictionary entry, try both cleanup modes. All of it works, because none of it needs a server. The only things that stop are activating a licence, the background re-check of it, and the update check — exactly the shape the three requests above predict.
  2. Run a per-application network monitor. Little Snitch and LuLu both attribute outbound connections to the process that made them. Leave one running through a day of ordinary use and you should see the daily feed fetch and nothing else; then press Activate with a key in the field, and one connection to the activation host should appear at that moment and not again.

The first check is the stronger of the two in one specific way. A network monitor tells you what an application asked for, while airplane mode tells you what it can still do when asking is not an option. Anything that depended on a server would fail the second test no matter how quiet it managed to be on the first.

The shape of a disclosure worth reading

A privacy claim written as a slogan asks you to believe something. A privacy claim written as a list of hosts, methods and fields asks you to check something, and it is falsifiable in a way a slogan is not — if any sentence above were wrong, a monitor running for one afternoon would show it.

The architecture that keeps the list this short is covered in on-device versus cloud speech recognition, and the question this article is the long answer to is handled directly in does dictation software send your voice to the cloud. The privacy policy covers what sits outside the application itself. If a network call is ever added, it will be named here and in the release notes with the same detail — host, trigger, fields, and the reason each field is in the request.

Related

All articles