Server libraries
You can install Vroxy by pasting the snippet into your layout by hand — see Installing the widget. The server libraries do the same thing for you, and add the part that’s tedious to hand-roll: telling Vroxy who the signed-in user is, with a server-side signature so access-gated bot tools actually unlock.
There are four:
| Package | For | Version |
|---|---|---|
vroxy (Ruby gem) |
Rails 7.1+ | 0.11.2 |
@vroxy/node |
Express / Connect, plus a Next.js component | 0.3.1 |
vroxy (Python) |
Django and Flask | 0.3.1 |
vroxy-support-chat |
WordPress 6.8+, PHP 7.4+ | 0.3.1 |
Availability
Section titled “Availability”None of these are published yet. Vroxy is pre-launch, so the
packages are private: the Node package is flagged private and is not
on npm, the Python package carries a Private :: Do Not Upload
classifier that makes PyPI reject it, the WordPress plugin is not in
the wp.org directory, and the gem is not on RubyGems.
Ask us and we’ll get you the package for your stack — installed from a repository or an archive, exactly the same code that will ship at launch. Public registry installs land when we launch.
What they all do
Section titled “What they all do”Set your workspace’s public key (from the workspace Embed page) and
every text/html response your app returns gets two script tags
appended before </body>:
<script src="https://vroxy.ai/widget.js?tenant=YOUR_PUBLIC_KEY" async></script><script> (function(){ window.vroxy = window.vroxy || function(){ (window.vroxy.q = window.vroxy.q || []).push(arguments); }; window.vroxy("identify", {"email":"[email protected]","name":"Ada Lovelace","external_id":"42","meta":{"role":"admin"}}); })();</script>The first tag is the widget loader. The second identifies the current
user through the same public vroxy(...) API a hand-rolled install
would use — which is why moving between a hand-rolled snippet and an
SDK (or between SDKs) is a no-op on the widget side. The snippet output
and the identity signatures are byte-identical across the libraries,
pinned by shared test vectors generated from the Ruby implementation.
The public key is not a secret; it’s in the page source of every site running the widget. The identity secret is a secret and never leaves your server.
The admin inspector
Section titled “The admin inspector”Every SDK also injects the in-page feedback inspector for an admin
whose identity is signed as admin — the same picker-and-note tool
behind Feedback to fix. The inspector only loads for a
signed admin-level identity, so a forged or unsigned role never gets
it. The WordPress plugin does the same, but gates at runtime in the
browser (its page is cache-safe, so the decision can’t be baked into
the HTML).
Middleware safety rules
Section titled “Middleware safety rules”The server SDKs only rewrite a response when all of the following hold, and pass the original bytes through untouched otherwise:
- The library is enabled and an API key is set.
- Auto-injection is on.
- The request path isn’t on your excluded-paths list.
- The
Content-Typeistext/html. - The status is 2xx (not 204, not a redirect, not an error page).
- The body contains a literal
</body>. - You didn’t already render the snippet yourself on this request.
Python adds one more: streaming and direct-passthrough responses (Django streaming responses, Flask static files) are never touched.
Identity verification
Section titled “Identity verification”Every SDK signs the identify payload when you give it the workspace
identity secret (Embed page → Identity verification). With the
secret set, the payload gains a level — admin when the resolved
role is in admin_roles, otherwise user — plus an HMAC-SHA256
signature over external_id|email|level.
Only a verified level unlocks the user and admin tiers of custom
bot tools. Unsigned identify calls still personalize
the conversation, they just don’t grant anything. The secret stays
server-side; only the derived signature for that one identity reaches
the page.
Full details, including the exact string that gets signed for hand-rolled integrations, are in Identifying visitors.
Error reporting
Section titled “Error reporting”Every SDK can ship exceptions from your app to your workspace’s Errors page — no separate error service needed. It’s on automatically in production when an API key is set, and can be forced either way.
Delivery is fire-and-forget on a background thread with 3-second
timeouts and a 60-per-minute throttle, so reporting can never
slow down or break your app. Reports go to <endpoint>/ingest/errors,
authenticated with a secret_token (a workspace API token with
tenant:write) in the Authorization: Bearer header — without one,
nothing is sent, because the public key is in every page’s source and
a forged source would otherwise pollute the error list.
- Ruby — subscribes to Rails’ error reporter, so every unhandled
request or job exception is delivered. Report a handled one with
Vroxy.report_error(e, context: { order_id: order.id }). - Node —
reportError(e, { context: {...} })for handled errors;installProcessHandlers()opts in touncaughtException(via the non-intrusive monitor hook) andunhandledRejection, both reported as unhandled, with default crash semantics preserved. - Python —
vroxy.report_error(error, context={...});vroxy.install_excepthook()andvroxy.VroxyLoggingHandler()are opt-in for unhandled and logged exceptions. - WordPress — optional; when enabled, fatal PHP errors are reported.
Each SDK has an ignore list of exception classes that are never reported (subclasses included) and that you can extend.
See Error reporting for what the endpoint accepts, how errors are grouped, and the retention limits.