Documentation
Deploy your Versiq conversion Agent on your site in minutes. Script tag or NPM, your choice.
Your data (catalogue · CRM · knowledge base) → Agent
Agent → events → Your CRM / analytics
The Agent lives in the portal
You configure it once (vertical, brand, objectives). The widget is its display channel, not the Agent itself.
Your data grounds the answers
Connect your sources (inventory, CRM, knowledge base): the Agent answers from your real data, not the public-web average.
The publishable key ties it together
A pk_* key binds the embedded widget to an Agent and its configuration, resolved at load time.
Choose your approach
Ideal for: WordPress, Shopify, static sites
SeeIdeal for: React, Next.js, Vue
SeeIdeal for: Discovery, evaluation
Try1. Create the Agent in the portal
In /app/portal, create an Agent: give it a name and pick its vertical (real estate, B2B…).
2. Grab a test key
Generate a pk_test_* key from /app/portal/api-keys. It works on localhost with no domain configuration.
3. Embed the widget
Paste the script tag (below) with your key into a locally-served page. No build step.
4. Converse
Open the page: the Agent starts the conversation. Hook the ready or qualified event to react on your side.
<!-- Add this script to your page -->
<!-- Production tip: pin a version + SRI, e.g. @0.2.0 + integrity="sha384-..." -->
<script
src="https://unpkg.com/@dolard.eu/versiq-widget@latest/dist/widget.umd.js"
data-api-key="pk_your_key"
></script>The widget intentionally splits two kinds of publishable keys. A test key (pk_test_*) is designed for local development: it always works on localhost, 127.0.0.1 and *.localtest.me, with zero domain configuration. A live key (pk_live_*) is designed for public environments: it refuses localhost and requires an explicit domain whitelist.
1. Local development
Key: pk_test_*- Domain: no declaration required — test keys always allow localhost, 127.0.0.1 and *.localtest.me.
Create a test key from /app/portal/api-keys, embed the widget on http://localhost:3000 (or your dev server port), and exercise every feature without touching the Domains page.
2. Staging / preview
Key: pk_live_* (dedicated to staging)- Domain to declare: https://staging.mysite.com (exact URL of your preview environment)
Create a live key dedicated to staging — never reuse the production key. Add the exact URL of your preview environment in /app/portal/domains. If your staging runs on a custom port (e.g. preview-42.mysite.com:8443), add the full URL with port.
3. Production
Key: pk_live_*- Domain to declare: https://mysite.com (or wildcard *.mysite.com to cover several subdomains at once)
For a site that serves several subdomains (app, www, checkout…), a *.mysite.com wildcard is more maintainable than multiple exact entries. Note: the wildcard only covers direct subdomains — add mysite.com as an extra entry if the apex domain also serves the widget.
Why split test and live?
A pk_live_* live key refuses localhost even in dev and requires a domain whitelist. This is intentional: if a live key is stolen (leak in a public GitHub repo, compromised browser extension, accidental screenshot), it cannot be used from a local HTML file or from any undeclared third-party site. An attacker who gets the key cannot do anything with it without also controlling one of the domains you explicitly allowed. This is the seat belt that compensates for the key being publicly exposed in your pages' HTML.
Wildcard syntax
A *.mysite.com wildcard matches app.mysite.com, www.mysite.com, checkout.mysite.com — but not mysite.com itself. To cover the apex domain, add it as a separate entry. Wildcards only support one level (*.mysite.com ≠ **.mysite.com): a.b.mysite.com is not matched, you need to add *.b.mysite.com as well.
What happens if the domain is not allowed?
The Versiq server returns a 403 ORIGIN_NOT_ALLOWED error and the widget stays silent (no chat window opens). Enable the widget's debug option during integration to see the response in the console — it is usually the first thing to check when the widget fails to appear in staging.
npm install @dolard.eu/versiq-widgetimport { createWidget } from "@dolard.eu/versiq-widget";
const widget = createWidget({ apiKey: "pk_your_key" });
// Control the widget
widget.open();
widget.close();
widget.reset();| Option | Type | Default | Description |
|---|---|---|---|
| data-api-key | string | required | Publishable API key (pk_*) — required, identifies your Agent |
| data-container | string | - | CSS selector for the inline mode container (e.g., '#chat'). The programmatic SDK also accepts a raw HTMLElement. |
| data-base-url | string | Production URL | Base URL for the embed |
| data-debug | boolean | false | Enable debug logging |
| data-email | string | - | Pre-identified visitor email (HMAC verification) |
| data-user-id | string | - | Host-side unique user identifier (HMAC verification) |
| data-user-hash | string | - | HMAC-SHA256 of the userId (or email as fallback), signed with the identity secret |
Floating
The widget appears as a floating button in a corner of the page. This is the default mode — no HTML option required, position is set in /app/portal/widget.
Inline
The widget is embedded directly into a container on your page. Ideal for a contact page or dedicated landing page. Pass a CSS selector via data-container, or an HTMLElement via the programmatic SDK.
import { createWidget } from "@dolard.eu/versiq-widget";
// container accepts a CSS selector OR an HTMLElement
const widget = createWidget({
apiKey: "pk_your_key",
container: "#chat-container",
});Script Tags
<!-- B2B qualification widget -->
<script
src="https://unpkg.com/@dolard.eu/versiq-widget@latest/dist/widget.umd.js"
data-api-key="pk_your_b2b_key"
></script>
<!-- Real estate widget (same page) -->
<script
src="https://unpkg.com/@dolard.eu/versiq-widget@latest/dist/widget.umd.js"
data-api-key="pk_your_immo_key"
></script>Programmatic API
import { createWidget } from "@dolard.eu/versiq-widget";
// Each API key maps to an Agent with its own vertical, theme, and
// position — all configured from the admin portal.
const b2bWidget = createWidget({ apiKey: "pk_your_b2b_key" });
const immoWidget = createWidget({
apiKey: "pk_your_immo_key",
// container pins the widget to a specific DOM node (inline mode).
container: document.getElementById("demo-immo"),
});
// Access instances after they're ready
b2bWidget.on("ready", () => {
// window.VersiqApps.get(b2bWidget.applicationId) → VersiqAPI
console.log("B2B widget ready:", b2bWidget.applicationId);
});Theming is configured in the portal
Since release 2024.11, theming (colors, typography, border radius, light/dark scheme, position) is managed from /app/portal/widget. The widget resolves these settings at load time via the publishable key — no HTML changes required.
Open portal →Toggle light/dark at runtime
To sync the widget's color scheme with your host site (user toggle, system preference), use the JavaScript API window.Versiq.setColorScheme('light' | 'dark' | 'auto').
Versiq connects to your data via a generic interface. Each source declares its capabilities, and AI tools adapt automatically.
DataSource Interface
Implement this TypeScript interface to connect your own data source.
interface DataSource {
readonly id: string;
readonly displayName: string;
readonly coverageDescription: string;
getCapabilities(): ReadonlySet<DataSourceCapability>;
search?(params: Record<string, unknown>): Promise<unknown[]>;
getStats?(zone: string): Promise<Record<string, unknown>>;
getAreaMetrics?(zone: string, filters: Record<string, unknown>): Promise<Record<string, unknown>>;
estimate?(params: Record<string, unknown>): Promise<Record<string, unknown>>;
count?(areas: string[], filters: Record<string, unknown>): Promise<number>;
checkCoverage?(zone: string): Promise<{ covered: boolean; message?: string }>;
}Capabilities
Each capability enables one or more AI tools in the conversation.
| Capability | Method | Tools | Description |
|---|---|---|---|
search | search() | searchProperties | Search items/products with filters |
stats | getStats() | getCityStats | Aggregated statistics by zone |
areaMetrics | getAreaMetrics() | getAreasOverview | Detailed metrics by zone (overview) |
estimate | estimate() | estimateProperty, estimateRent | Price or value estimation |
count | count() | getPropertyCount | Count with filters |
coverage | checkCoverage() | checkZoneCoverage | Zone coverage verification |
Example: REST API
A concrete example connecting to a third-party REST API.
class MyApiDataSource implements DataSource {
readonly id = "my-api";
readonly displayName = "Mon API Produits";
readonly coverageDescription = "Catalogue produit via API REST";
getCapabilities() {
return new Set(["search", "count"]);
}
async search(params) {
const res = await fetch(`https://api.example.com/products?${qs(params)}`);
return res.json();
}
async count(areas, filters) {
const res = await fetch("https://api.example.com/products/count", {
method: "POST",
body: JSON.stringify({ areas, filters }),
});
const data = await res.json();
return data.count;
}
}Graceful Degradation
When a source only supports certain capabilities, unsupported tools are automatically hidden. Partners don't need to configure anything on the tools side.
searchstatsestimatecountareaMetricscoverageAccess modes
Versiq adapts to your infrastructure. The custom mode (DataSource) is available today; native connectors and standard protocols are on the way.
When to use
Identity verification is designed for authenticated areas where visitors are already logged in — SaaS dashboards, client portals, member areas. It is not intended for public marketing pages where visitors are anonymous.
Identity Levels
| Option | Type | Default | Description |
|---|---|---|---|
| Anonymous | Cookie / session ID | - | Default for all visitors — no setup required |
| Identified | Email shared in chat | - | Visitor shares their email during conversation |
| Verified | HMAC attested by host | - | Host site cryptographically attests the visitor's identity — zero friction |
Step 1: Compute HMAC server-side
The HMAC must be computed on your backend. Never expose the secret in client-side code.
Node.js
import crypto from "node:crypto";
// Sign the stable userId when you have one (recommended), else the email.
// Send both data-user-id and data-user-hash; data-email stays optional.
const signedValue = user.id ?? user.email;
const hmac = crypto
.createHmac("sha256", process.env.VERSIQ_IDENTITY_SECRET)
.update(signedValue)
.digest("hex");Python
import hmac
import hashlib
import os
# Sign the stable user id when you have one (recommended), else the email.
# Send both data-user-id and data-user-hash; data-email stays optional.
signed_value = user.id or user.email
user_hash = hmac.new(
os.environ["VERSIQ_IDENTITY_SECRET"].encode(),
signed_value.encode(),
hashlib.sha256,
).hexdigest()Step 2: Pass identity to the widget
Add the identity attributes to your widget script tag, or use the programmatic API after login.
Script tag
<script
src="https://unpkg.com/@dolard.eu/versiq-widget@latest/dist/widget.umd.js"
data-api-key="pk_live_..."
data-email="${user.email}"
data-user-id="${user.id}"
data-user-hash="${hmac}"
></script>Dynamic (post-login)
For single-page apps, identify the visitor after login without reloading the page.
// After user logs in, identify them dynamically
window.Versiq.identify({
email: "user@example.com",
userId: "usr_123",
userHash: "<computed_hmac>",
});HMAC Input
The HMAC is computed on userId if provided (stable identifier, recommended), or email as fallback. Using userId means the HMAC stays valid even if the user changes their email.
Security
The identity secret must never appear in client-side code. Generate it in your Versiq admin dashboard under Settings > Identity Verification.
| Option | Type | Default | Description |
|---|---|---|---|
| ready | - | - | Widget is loaded and ready |
| open | - | - | Widget was opened |
| close | - | - | Widget was closed |
| message | WidgetMessage | - | New message in the conversation |
| profile-update | WidgetProfile | - | User profile was updated |
| qualified | { profile, score } | - | Lead qualified with relevance score |
| quota-warning | { remaining, limit } | - | API quota is running low |
| quota-exceeded | - | - | API quota has been exceeded |
| error | { code, message } | - | An error occurred |
| identity-verified | { email, userId? } | - | Visitor identity was verified via HMAC |
Usage example
// Subscribe to events
window.Versiq.on("ready", () => {
console.log("Agent is ready");
});
window.Versiq.on("profile-update", (data) => {
console.log("Profile updated:", data.profile);
});
window.Versiq.on("qualified", (data) => {
console.log("Lead qualified:", data.profile);
console.log("Score:", data.score);
// Send to your CRM
});
window.Versiq.on("identity-verified", (data) => {
console.log("Identity verified:", data.email);
});
window.Versiq.on("quota-warning", (data) => {
console.warn("Quota low:", data.remaining, "/", data.limit);
});
window.Versiq.on("error", (data) => {
console.error("Error:", data.code, data.message);
});| Option | Type | Default | Description |
|---|---|---|---|
| open() | void | - | Open the widget |
| close() | void | - | Close the widget |
| reset() | void | - | Reset the conversation |
| setTheme(theme) | void | - | Change theme dynamically |
| setColorScheme(scheme) | void | - | Toggle the color scheme ('light' | 'dark' | 'auto') — handy for wiring the host site's own theme switch |
| on(event, handler) | void | - | Subscribe to an event |
| off(event, handler) | void | - | Unsubscribe from an event |
| destroy() | void | - | Remove widget and cleanup resources |
| identify(params) | void | - | Identify a visitor with HMAC verification |
| version | string | - | Current SDK version (read-only) |
| applicationId | string | "" | Agent ID (applicationId) resolved after load (empty until ready is emitted) |
| window.VersiqApps | Map<string, VersiqAPI> | - | Global map of widget instances — key: applicationId, value: VersiqAPI |