Identity verification for the widget
Why identity verification matters
Without verification, the widget trusts whatever the browser tells it. A visitor could open DevTools and call AskVault.identify({user_id: "ceo@yourco.com"}) to claim to be the CEO. The bot would then leak content audience-tagged for CEO eyes.
With verification, your backend signs the visitor's user_id with a secret only your server knows. AskVault checks the signature. The browser can't fake it.
Three flows depend on identity verification:
- Plan-aware content. Showing Enterprise-customer docs only to Enterprise visitors.
- Identity-gated skills.
subscription_managerwon't reveal billing details to unverified visitors. - Audience-tagged content. Internal documents tagged
employeeonly visible to verified team members.
How HMAC verification works
Step-by-step:
- You and AskVault share a secret string (32 to 64 characters, generated once).
- For each logged-in visitor, your backend computes
hash = HMAC_SHA256(secret, user_id). - Your frontend passes
user_id, the hash, and optional metadata to the widget. - AskVault recomputes the hash server-side using the same secret.
- If hashes match, the visitor is verified. If not, the request is rejected with HTTP 403.
The browser never sees the secret. An attacker who tampers with user_id would need to forge a matching hash, which requires the secret.
Setup walkthrough
About 15 minutes for a typical backend.
Step 1: generate the secret
- Open Workspace Settings > Identity Verification.
- Click "Generate Secret".
- Copy the secret immediately. AskVault hashes the secret for storage and won't show it again after page reload.
- Store the secret in your backend's secret manager (environment variable, AWS Secrets Manager, Vault, etc.).
The secret looks like a 64-character hex string. Never check it into git, log it, or expose it client-side.
Step 2: compute the hash on your backend
Per logged-in visitor:
Node.js example.
const crypto = require('crypto');const secret = process.env.ASKVAULT_IDENTITY_SECRET;const userId = currentUser.id; // e.g., "user_12345"const hash = crypto.createHmac('sha256', secret).update(userId).digest('hex');Python example.
import hmac, hashlib, ossecret = os.environ["ASKVAULT_IDENTITY_SECRET"]user_id = current_user.id # e.g., "user_12345"hash_value = hmac.new( secret.encode("utf-8"), user_id.encode("utf-8"), hashlib.sha256).hexdigest()Ruby example.
require 'openssl'secret = ENV['ASKVAULT_IDENTITY_SECRET']user_id = current_user.idhash = OpenSSL::HMAC.hexdigest('SHA256', secret, user_id)Pass user_id and hash to your frontend (e.g., as data attributes on a hidden DOM element or a global JS variable).
Step 3: identify the visitor in the widget
In your frontend, after the widget script loads:
window.AskVault.identify({ user_id: "user_12345", hash: "abc123...64hexchars", name: "Alice Chen", email: "alice@yourco.com", plan: "enterprise"});The name, email, plan, and any additional attributes are optional. Only user_id and hash are required.
Step 4: enable enforcement
By default, identity is verified-if-provided but not required. Once you've confirmed the hash flow works, enforce it:
- Workspace Settings > Identity Verification.
- Toggle "Enforce identity verification".
- Save.
After enforcement, widget requests without a valid hash get HTTP 403. Anonymous visitors can still chat, but won't get audience-tagged content.
What the bot does with verified identity
Three changes when a visitor is verified:
- Plan-aware retrieval. The retrieval layer filters knowledge by the visitor's plan tag. An Enterprise visitor sees Enterprise-only docs; a Free visitor doesn't.
- Skill access. Identity-gated skills (subscription_manager, refund_processor) only fire for verified visitors.
- Audit trail. Every conversation logs
identity_verified: trueplus the verifieduser_idfor compliance.
Unverified visitors see only public-audience content and can't trigger identity-gated skills.
Field reference
The identify() method accepts:
| Field | Required | Description |
|---|---|---|
user_id | Yes | Your unique ID for the visitor. Used for the HMAC. |
hash | Yes | HMAC-SHA256 of user_id with the shared secret. |
name | No | Display name. Used in welcome message. |
email | No | Email address. Used by lead-capture and ticketing skills. |
plan | No | Audience tag for plan-aware content (e.g., "enterprise", "growth"). |
attributes | No | Free-form JSON for additional context (company size, role, etc.). |
Server-side verification details
Per the actual implementation:
expected = HMAC_SHA256(secret, user_id)verified = constant_time_compare(expected, hash)A constant-time comparison prevents timing attacks. The HMAC uses SHA-256 (not SHA-1 or MD5).
If user_id is missing, the visitor is treated as anonymous. If hash is missing but enforcement is on, the request fails with 403.
Audit and compliance
Every conversation records:
- identity_verified:
trueorfalse. - user_id: when verified.
- Timestamp of the verification.
Visible in the conversation detail view and exportable via the conversations API. Useful for SOC 2 access-control evidence and GDPR data-subject-access requests.
Rotating the secret
Best practice: rotate the secret every 6 to 12 months, or immediately if compromise is suspected.
- Generate a new secret (Workspace Settings > Identity Verification > Generate New Secret).
- A 24-hour grace period activates during which both old and new secrets verify.
- Update your backend to compute hashes with the new secret.
- After the grace period, the old secret is automatically retired.
The grace window prevents downtime during rotation. Users with cached pages keep working until they refresh.
Common pitfalls
Hash mismatch on every request. Usually one of: wrong secret on backend, hashing the email instead of user_id, encoding mismatch (UTF-8 vs ASCII), or whitespace in the user_id.
Verified visitors still see public content. Audience tags not applied to the knowledge source. Tag relevant docs under Knowledge Hub > [doc] > Audience.
HTTP 403 after enabling enforcement. Some pages on your site don't pass the identity. Either pass identity on every page or limit enforcement to authenticated routes (not the public widget).
Browser console shows the secret. Never expose the secret in client-side code. Compute the hash server-side only.
Hash exposed in URL. Don't put the hash in a query string; it ends up in browser history and server logs. Pass it via JavaScript only.
Planned enhancements (on the roadmap)
Features documented for accuracy but not yet shipped:
- Per-session token rotation. Today the hash is stable per user_id; planned to support short-lived per-session tokens.
- JWT-based identity. As an alternative to HMAC, accept signed JWTs from your IdP. Useful for teams already issuing JWTs.
- Native SAML/OIDC bridge. Forward an IdP assertion directly without a backend hashing step. Targeting Enterprise.
Each ships when prioritized. The HMAC flow described above is production-ready today.
FAQ
Do I need identity verification for an anonymous-visitor widget?
No. Identity verification is for logged-in visitors. Anonymous-only deployments skip it.
Can I use email as the user_id?
Yes if the email is a stable identifier. Best practice is to use your internal user ID (immutable) rather than email (changeable).
How long is the hash valid?
Indefinitely. The hash is a function of user_id and the secret, both of which don't change per request. Rotate the secret periodically.
What if my user_id changes for a visitor?
Compute a fresh hash. The old hash for the previous user_id won't verify the new one.
Is this secure against replay attacks?
The hash itself isn't replay-protected (same user_id always hashes the same). For higher-stakes flows, combine with session timestamps or use the planned JWT-based identity flow.