Skip to content

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

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.

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.

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).

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-Type is text/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.

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.

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 to uncaughtException (via the non-intrusive monitor hook) and unhandledRejection, both reported as unhandled, with default crash semantics preserved.
  • Python — vroxy.report_error(error, context={...}); vroxy.install_excepthook() and vroxy.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.