Triggers & JavaScript API
The floating button is the default, not the only option. You can open the panel from your own UI, from a keyboard shortcut, or at a moment that matters in your product, and attach who the person is before they write a word.
Open it from your own button
Put data-voicebox-trigger on any element. No JavaScript, and it works on elements added to the page later, since the listener is delegated from document.
<button data-voicebox-trigger>Send feedback</button>
<!-- Works on links, menu items, anything clickable -->
<a href="#" data-voicebox-trigger>Report a problem</a>Turn off the floating button under Widget → Trigger when you are supplying your own, otherwise you will have two ways in.
Clicks during page load are not lost
The JavaScript API
A single global function, available as soon as the script runs.
| Call | Does |
|---|---|
| Voicebox("open") | Opens the panel. Queues if still loading. |
| Voicebox("close") | Closes the panel. |
| Voicebox("identify", {…}) | Attaches traits to every submission from this visitor. |
A keyboard shortcut
document.addEventListener("keydown", (e) => {
// ⌘/ or Ctrl+/ opens feedback from anywhere
if ((e.metaKey || e.ctrlKey) && e.key === "/") {
e.preventDefault();
Voicebox("open");
}
});Esc closes the panel already, you do not need to wire that up.
Ask at the right moment
The best feedback arrives just after something notable happened. Open the panel then, rather than hoping people find the button later.
// After a task that took real effort
async function onExportFinished() {
Voicebox("identify", { lastAction: "export", exportRows: 12400 });
Voicebox("open");
}
// After someone hits an error, while it's fresh
function onPaymentFailed(code) {
Voicebox("identify", { errorCode: code, screen: "checkout" });
Voicebox("open");
}Do this sparingly
Attaching who they are
Call identify once you know who the visitor is, usually right after your own auth resolves. Traits are held for the session and attached to anything they submit, so feedback arrives with the account already on it and you can reply without asking who they are.
Voicebox("identify", {
userId: "usr_8123",
plan: "pro",
company: "Acme",
signedUpAt: "2026-02-14",
seats: 25,
});Calls merge rather than replace, so you can add traits as you learn them. Up to 30 keys and 4KB per submission. Go over and the extra traits are trimmed, never the message: an over-filled identify call should not cost you what somebody took the time to write.
Traits never reach the model
Collecting without the widget
A mobile app, a CLI, a support inbox, a Discord bot: anything that can make an HTTP request can file feedback, and it lands in the same inbox and the same themes as widget submissions.
curl -X POST https://www.usevoicebox.dev/api/ingest \
-H "Content-Type: application/json" \
-d '{
"key": "pk_live_your_key",
"body": "The CSV export drops the last row.",
"type": "ISSUE",
"rating": 2,
"email": "sam@acme.com",
"metadata": { "source": "ios-app", "version": "3.4.1" }
}'Full request and response shape is in the API reference. Note that server-to-server calls have no Origin header, so if you have set a domain allowlist you will need to send from the browser or leave the allowlist empty for that project.