Security & privacy
The widget runs on your users' browsers, on your domain. This page is the full account of what it can do, what it sends, and how to lock it down.
Your project key is public
Keys starting pk_ are publishable. They sit in your page source where anyone can read them, and that is fine, by design. A project key can do exactly one thing: submit feedback to that project.
| A project key can | A project key cannot |
|---|---|
| Post a new piece of feedback Read the widget's own appearance settings | Read any feedback, yours or anyone else's See themes, members, or account data Change any setting |
API keys are different
sk_, created under Settings → Developers, read your feedback and themes. Those are secret. Never put one in front-end code, and rotate it immediately if it leaks.Lock it to your domains
Add your domains under Settings → Projects. Once the list is non-empty, submissions are only accepted from those origins, so somebody who copies your key out of your page source cannot post with it from theirs.
An empty list means any origin, which is the right default for getting started and the wrong one to stay on. Subdomains are matched, so acme.com covers app.acme.com.
Server-side calls and the allowlist
Origin header, and a project with an allowlist refuses those rather than waving them through, since a missing header is trivially forged. If you post from a server or a mobile app, use a project with an empty allowlist for that traffic.Content Security Policy
If your site sends a CSP header, the widget needs two entries: one to load the script, one to reach the API. Without them the browser blocks it silently and the only trace is a console error.
script-src 'self' https://www.usevoicebox.dev;
connect-src 'self' https://www.usevoicebox.dev;That is the whole list. The widget loads no fonts, no images, and no third-party code, so font-src, img-src and frame-src need nothing added.
Its styles live inside a shadow root and are injected as a stylesheet, so a style-src without 'unsafe-inline' can still block them, leaving an unstyled panel rather than no panel. If yours is strict, allow the widget origin there too.
It cannot break your page
- Style isolation. Everything renders in a Shadow DOM root with
:host { all: initial }. Your CSS cannot reach in, including!importantrules on*, and nothing we ship leaks out. - No globals but one. The script defines
window.Voiceboxand nothing else. It does not patch built-ins, add polyfills, or touchfetch. - Fails quietly. If our API is unreachable the widget does not render and throws nothing into your error tracking. A feedback tool having a bad day must never take a customer's site with it.
- Never blocks. Loaded async and mounted on an idle callback, so it renders after your page is interactive, not before.
What actually gets sent
On submit, and only on submit. Nothing is transmitted while somebody is merely browsing your site.
{
"body": "the message they wrote",
"type": "IDEA | ISSUE | PRAISE | QUESTION | OTHER",
"rating": 1-5, or null if you turned the scale off,
"email": only if they chose to type one,
"pageUrl": the page they were on when they sent it,
"referrer": document.referrer, if any,
"locale": navigator.language,
"metadata": whatever you passed to identify()
}There are no cookies, no localStorage, no fingerprinting, and no cross-site tracking of any kind. The widget cannot see your other pages, your users' sessions, or anything they type outside its own textarea.
What reaches the AI provider
Three fields, and no others: the message body, the type they picked, and the rating. The email address and every identity trait are held back by construction, not by a filter that could be forgotten, so the model never receives anything that names a person.
You can switch analysis off entirely under Settings → General. Feedback is still collected, stored and shown, it simply is not scored or grouped, and nothing leaves for a model at all.
Spam and abuse
- No captcha. A honeypot field and a timing check catch scripted submissions without making a real person identify traffic lights.
- Rate limited per IP, so one source cannot flood a project.
- Origin checked against your allowlist on every request.
Retention and your users' rights
Submitter IP addresses are kept for seven days, purely to make the rate limit work, and then deleted automatically. Everything else is kept until you delete it.
You can export everything your workspace holds as JSON, or delete the workspace and all of it, from Settings → General, without asking us. For the contractual side, see the Data Processing Agreement and the Privacy Policy.
You are the controller