Skip to content

Identifying your users

By default every visitor is anonymous. If your page knows who’s viewing it, tell Vroxy so your team sees a real person in the transcript instead of a random token.

<script>
window.vroxy = window.vroxy || function () {
(window.vroxy.q = window.vroxy.q || []).push(arguments);
};
vroxy("identify", {
email: "ada@example.com",
name: "Ada Lovelace",
external_id: "42",
role: "admin",
meta: { plan: "pro", seats: 10 }
});
</script>

vroxy.identify({ … }) is equivalent to vroxy("identify", { … }) — use whichever reads better.

The shim in the first two lines is what makes ordering irrelevant. Calls made before the widget bundle finishes loading are queued and replayed once it’s ready, so you can call identify anywhere on the page relative to the loader script.

Field Limit What it’s for
email 320 characters Shown on the Visitors and Chats pages; also what chat search matches on.
name 200 characters Display name in the transcript and visitor list.
external_id 200 characters Your own primary key for this user, so you can pivot from a Vroxy conversation back to your record.
role 80 characters Your app’s role string. Informational only — see the warning below.
meta first 50 keys Anything else you want on the record. Scalar values are stored as strings; nested objects and arrays are kept as-is.

Notes:

  • Only the fields you actually send are updated. Identify never changes the visitor’s token, so the existing conversation and history stay attached.
  • Repeated calls overwrite. It’s safe to call on every page load.
  • Identified visitors show their name, email, external ID, and meta on the workspace Visitors page, and their identity appears in the Chats list and transcripts.

This part is a security boundary — read it carefully.

Custom bot tools each carry an access level: public (any visitor), user (a verified signed-in visitor), or admin (a verified admin). Because identify() runs in the browser, an unsigned claim proves nothing — anyone can open the console and type role: "admin". So role and meta never grant anything. They personalize the transcript and that’s all.

To unlock the gated tiers, your server signs the claim and passes two extra fields:

  • level"user" or "admin". ("public" is not a signable level; it’s the absence of a grant.)
  • signature — the hex HMAC-SHA256 of external_id + "|" + email + "|" + level, keyed with your workspace’s identity-verification secret. Missing fields are the empty string, and the fields never reorder.
<script>
vroxy("identify", {
email: "ada@example.com",
external_id: "42",
level: "admin",
signature: "SIGNATURE_COMPUTED_ON_YOUR_SERVER"
});
</script>

Vroxy recomputes the same canonical string from the parameters it actually received and compares it against your signature in constant time. That means the external_id and email you sign must be exactly the ones you send — downcase the email on the signing side only, or trim whitespace on one side and not the other, and verification fails silently (the visitor just stays public).

The signature covers only external_id, email, and level. name, role, and meta are outside it, which is fine because they don’t grant anything.

Where the secret lives: workspace sidebar → EmbedIdentity verification. It is a secret. It stays on your server; only the derived signature ever reaches the page, and a signature grants exactly the one identity it covers.

This is the rule that surprises people. The verified level reflects the latest identify call only:

  • An identify call with a valid level + signature sets the level.
  • An identify call with an invalid or mismatched signature resets the visitor to public.
  • An identify call with no level at all — including a partial one that just refreshes a display name — also resets the visitor to public.

That’s deliberate. When a user signs out and your page re-identifies without a signature, admin tools have to disarm immediately, especially on a shared browser. If you want a level to persist across identify calls, sign every one of them.

Access levels are enforced twice: gated tools aren’t offered to the model in the first place, and the level is checked again when a tool is actually called. The second check is the real boundary — models can hallucinate tool names, and a transcript can outlive the identity that started it.

The gem handles both halves. It reads current_user (or your custom config.identify resolver) and emits the identify call for you. Set the secret and it signs as well:

Vroxy.configure do |config|
config.identity_secret = ENV["VROXY_IDENTITY_SECRET"]
end

With the secret set, every injected identify call carries a level"admin" when the resolved role is in config.admin_roles (default: admin, owner), otherwise "user" — plus the matching signature. Without the secret the gem emits no level and no signature at all, rather than an unsigned claim that would be spoofable from the console.

If your notion of a widget admin isn’t a single role string, return an explicit level: from your config.identify block and the gem signs that instead.