Complete reference for the Convor widget browser API: route synchronization, visitor prefill, verified identity, metadata, events, and programmatic chat control.
The canonical widget loader exposes one browser API:
window.ConvorConvor controls the visible widget, iframe, and visitor session. No second
widget global or parallel SDK instance is created.
The dashboard-generated production embed loads the widget and exposes the API automatically:
<script
src="https://cdn.convor.io/widget.js"
data-key="convor_wpk_..."
async
></script>data-key is the public widget key copied from the dashboard. It starts with
convor_wpk_, is expected to be visible in browser code, and is not an
organization secret API key. The hosted bundle already contains the production
service endpoints; do not configure API, iframe, or realtime URLs in browser
code.
Wait for the visitor session before sending visitor or message commands:
const state = await Convor.ready();
console.log(state.visitorId);Load widget.js once. Convor.init(options) is available when an application
intentionally initializes the widget itself, but the normal embed does not need
an additional SDK script.
To prevent automatic initialization, load widget.js without data-key, then
call Convor.init() after the script has loaded:
<script id="convor-loader" src="https://cdn.convor.io/widget.js" async></script>
<script>
document.getElementById("convor-loader").addEventListener("load", async () => {
await Convor.init({
key: "convor_wpk_...",
locale: document.documentElement.lang,
});
});
</script>Do not add data-key to the loader when you want programmatic initialization;
data-key enables the normal automatic initialization path.
The normal embed reads the page language from <html lang> during
initialization. It does not watch later lang changes. When the application
language changes client-side, call Convor.setLocale(locale) instead of
re-initializing the widget.
setLocale() reloads the localized site configuration and built-in interface
catalog, then replaces only the iframe document. The existing launcher and host
frame stay mounted, including their open/closed state, visibility, and position.
The site-scoped visitor identity is preserved.
"use client";
import Script from "next/script";
import {useEffect, useRef, useState} from "react";
export function ConvorWidget({locale}: {locale: string}) {
const [loaded, setLoaded] = useState(false);
const lastLocale = useRef(locale);
useEffect(() => {
if (!loaded || !window.Convor || lastLocale.current === locale) return;
lastLocale.current = locale;
void window.Convor.setLocale(locale);
}, [loaded, locale]);
return (
<Script
src="https://cdn.convor.io/widget.js"
data-key="convor_wpk_..."
strategy="afterInteractive"
onLoad={() => setLoaded(true)}
/>
);
}The built-in widget interface currently has English and Polish catalogs. Other
requested locales can still select localized organization content when that
locale is configured, but Convor-owned interface strings fall back to English.
For example, Convor.setLocale("cs") can select Czech organization content
while the built-in widget controls remain in English.
For route changes that do not update the browser URL, call Convor.setPage()
separately; setPage() does not change the widget language.
await Convor.ready();
await Convor.setUser({
name: currentUser.name,
email: currentUser.email,
phone: currentUser.phone,
attributes: {
customerId: {label: "Customer ID", value: currentUser.id},
currency: {label: "Currency", value: currentUser.currency},
dropShipping: currentUser.isDropShipping,
language: {label: "Language", value: currentUser.language},
vatId: {label: "VAT ID", value: currentUser.vatId},
},
});setUser() immediately persists contact fields and attributes to the visitor
profile shown in the Convor dashboard. It also prefills matching pre-chat form
fields.
Trusted and self-reported identity
Use setUser() for convenient browser-side prefill and metadata. Use
identify() with a server-generated HMAC when the account identity must be
trusted. Browser code can never overwrite contact fields belonging to a
verified or operator-managed profile.
The loader automatically detects standard browser navigation, including
history.pushState(), history.replaceState(), popstate, and hashchange.
For routers that do not update the browser URL, send the logical route
explicitly:
await Convor.setPage({
url: "/account/orders/ORD-1042",
referrer: "/account/orders",
});Relative URLs are resolved against the current page. Only HTTP and HTTPS URLs are accepted. A successful route update:
page:change on the host page.track("page_view") is an alias intended for analytics-style integrations:
await Convor.track("page_view", {
url: window.location.href,
referrer: document.referrer,
});Custom event names are not currently accepted by track().
Convor.setUser(user)Sets self-reported contact data, application metadata, and pre-chat values. Every field is optional.
await Convor.setUser({
name: "Jane Customer",
email: "[email protected]",
phone: "+48 123 123 123",
department: "billing",
customFields: {
"company-field-id": "Acme Sp. z o.o.",
"accepted-terms-field-id": true,
},
attributes: {
customerId: {label: "Customer ID", value: 1042},
plan: {label: "Plan", value: "Scale"},
overdue: false,
},
});| Field | Type | Persistence |
|---|---|---|
name | string | null | Visitor profile and matching pre-chat field |
email | string | null | Visitor profile and matching pre-chat field |
phone | string | null | Visitor profile and matching pre-chat field |
attributes | Record<string, AttributeValue> | Visitor metadata shown in the dashboard |
department | string | null | Pre-chat department selection |
customFields | Record<string, string | number | boolean> | Configured pre-chat custom fields, keyed by custom-field ID |
Use the custom-field IDs present in the configured pre-chat form, not their display labels. Up to 50 custom fields may be supplied, and each ID is limited to 100 characters.
Passing null clears a self-reported contact value. Contact fields are ignored
when the current profile is verified or operator_entered; attributes remain
updatable because they describe application context rather than identity.
Partial pre-chat data is saved as a draft. It does not mark the form as completed and cannot bypass required fields.
Convor.setAttributes(attributes)Updates application-defined visitor attributes without changing contact data:
await Convor.setAttributes({
plan: "team",
seats: 12,
trial: false,
customerId: {label: "Customer ID", value: "CUS-1042"},
});The aliases setVariables() and setMetadata() have the same behavior.
An attribute value can be:
string | number | boolean | {
label: string;
value: string | number | boolean;
} | nullUse a labeled value when the dashboard label should differ from the programmatic
key. Set a value to null to remove that attribute. A visitor can have at most
50 attributes; keys are limited to 100 characters, labels to 100 characters,
and string values to 2,000 characters. The prototype-control keys __proto__,
prototype, and constructor are reserved.
Convor.identify(identity)Use identify() when your application already has an authenticated customer and
you want Convor to mark that visitor identity as verified. Generate the signing
secret in Settings → Widget → Installation and security → Verified customer identity, copy it when it is
shown, and store it only in your application's server-side secret store.
The signing flow has two trust boundaries:
Do not accept arbitrary customer identity fields from the browser and sign them without checking them against your authenticated account data.
First obtain the current visitor ID in the browser:
const {visitorId} = await Convor.ready();
const identity = await fetch("/api/convor-identity", {
method: "POST",
credentials: "include",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({visitorId}),
}).then((response) => response.json());
await Convor.identify(identity);On your application server, authenticate the request, load the current customer, and compute HMAC-SHA256 over the exact UTF-8 JSON produced by the following property order. Missing optional values must be empty strings:
import {createHmac} from "node:crypto";
export async function buildConvorIdentity({visitorId, session}) {
const customer = await loadCustomerForSession(session);
const traits = {
id: customer.id,
email: customer.email ?? undefined,
name: customer.name ?? undefined,
phone: customer.phone ?? undefined,
};
const payload = JSON.stringify({
visitorId,
id: traits.id,
email: traits.email ?? "",
name: traits.name ?? "",
phone: traits.phone ?? "",
});
const userHash = createHmac(
"sha256",
process.env.CONVOR_IDENTITY_SECRET
)
.update(payload)
.digest("hex");
return {...traits, userHash};
}The exact values passed to Convor.identify() for id, email, name, and
phone must match the values used to generate the hash. userHash is a
64-character hexadecimal HMAC-SHA256 digest. Application attributes are not
part of this signature; send them with setAttributes() after identification
when needed.
The signature is also bound to the current Convor visitorId. Convor requires
the public widget key and the active visitor session to belong to the same site
and visitor before accepting the identity, so a valid hash generated for one
visitor cannot be replayed for another. Rotating or disabling the site's
identity secret stops future requests signed with the previous secret from
verifying.
Convor.getVisitorId()Returns the current visitor UUID after ready() resolves, otherwise null:
const visitorId = Convor.getVisitorId();Convor.open();
Convor.close();
Convor.toggle();openChat() and closeChat() are aliases for open() and close().
Convor.hide(); // Hides both launcher and panel.
Convor.show();Hiding the widget does not destroy the visitor session or disconnect the conversation.
Convor.setLocale(locale)Changes the mounted widget language at runtime:
await Convor.setLocale("pl-PL");Pass a non-empty BCP 47-style locale such as en, en-US, or pl-PL.
Convor resolves the best enabled organization-content locale on the server and
uses the closest built-in interface catalog. The promise resolves after the new
iframe document reports that it has mounted. Commands that require the visitor
session continue to wait for the refreshed iframe session to become ready.
If the locale configuration request fails, the currently mounted widget stays unchanged. When several locale changes overlap, only the latest request is applied.
const state = Convor.getState();
// {
// ready: boolean,
// open: boolean,
// visible: boolean,
// unreadCount: number,
// visitorId: string | null,
// pageUrl: string
// }Convenience methods are also available:
Convor.isOpen();
Convor.isVisible();Convor.setDraft(message)Prefills the composer without sending:
await Convor.setDraft("I need help with order ORD-1042");
Convor.open();The draft is limited to 4,000 characters and uses the normal widget draft persistence.
Convor.sendMessage(message)Sends a visitor message through the active widget session:
const sent = await Convor.sendMessage(
"Please connect me with the billing team."
);The promise resolves with:
{
id: string;
content: string;
senderType: "visitor";
createdAt: string;
}If a pre-chat form is enabled, every configured required field must already be
completed through the form or setUser(). Programmatic sending cannot bypass
the form.
Register listeners with on(), remove them with off(), or register a
single-use listener with once():
const onIncomingMessage = (message) => {
console.log(message.content);
};
Convor.on("message:received", onIncomingMessage);
Convor.off("message:received", onIncomingMessage);
Convor.once("open", (state) => {
console.log("First open", state);
});| Event | Payload |
|---|---|
ready | Complete widget state |
open | Complete widget state |
close | Complete widget state |
show | Complete widget state |
hide | Complete widget state |
unread:change | {count, state} |
message:sent | Message object |
message:received | Message object |
page:change | {url, referrer?} |
visitor:updated | {visitorId} |
reset | State for the new anonymous visitor |
destroy | undefined |
Message events use this shape:
{
id: string;
content: string;
senderType: "visitor" | "operator" | "system";
createdAt: string;
}Use ready() rather than relying on a ready listener when the listener may be
registered after initialization.
Convor.resetVisitor()Starts a fresh anonymous visitor and reloads the iframe session:
await Convor.resetVisitor();Use it after the host application logs a user out or switches accounts. The
promise resolves after the new visitor session is ready. logout() is an alias.
Convor.destroy()Removes the launcher, iframe, styles, listeners, route observers, and active API requests:
Convor.destroy();Call init() again to create a new widget instance.
| Method | Returns | Purpose |
|---|---|---|
init(options?) | Promise<WidgetState> | Programmatically create the widget loader |
ready() | Promise<WidgetState> | Wait for visitor authentication and API readiness |
open() / openChat() | void | Open the panel |
close() / closeChat() | void | Close the panel |
toggle() | void | Toggle the panel |
show() | void | Show launcher and panel |
hide() | void | Hide launcher and panel |
isOpen() | boolean | Read open state |
isVisible() | boolean | Read visibility state |
getState() | WidgetState | Read complete synchronous state |
getVisitorId() | string | null | Read current visitor ID |
setPage(page) | Promise<void> | Synchronize a logical route |
track("page_view", properties?) | Promise<void> | Record a page view |
setUser(user) | Promise<void> | Set contact data, metadata, and pre-chat values |
identify(identity) | Promise<void> | Apply server-verified customer identity |
setAttributes(attributes) | Promise<void> | Update visitor application attributes |
setVariables(attributes) | Promise<void> | Alias of setAttributes() |
setMetadata(attributes) | Promise<void> | Alias of setAttributes() |
setDraft(message) | Promise<void> | Prefill the composer |
sendMessage(message) | Promise<Message> | Send a visitor message |
on(event, listener) | void | Add an event listener |
off(event, listener) | void | Remove an event listener |
once(event, listener) | void | Add a one-shot listener |
resetVisitor() / logout() | Promise<void> | Start a new anonymous visitor |
destroy() | void | Remove the widget instance |
The following client component mirrors a common Smartsupp-style integration: it waits for account loading, sends contact fields and labeled variables, and updates the logical page on App Router navigation.
"use client";
import Script from "next/script";
import {usePathname, useSearchParams} from "next/navigation";
import {useCallback, useEffect, useState} from "react";
export function ConvorChat({user, isLoading}) {
const pathname = usePathname();
const searchParams = useSearchParams();
const [scriptReady, setScriptReady] = useState(false);
const syncUser = useCallback(async () => {
if (!window.Convor || isLoading) return;
await window.Convor.ready();
await window.Convor.setUser({
name: user?.name ?? null,
email: user?.email ?? null,
phone: user?.phone ?? null,
attributes: {
customerId: {
label: "Customer ID",
value: user?.id ?? "Guest",
},
currency: {
label: "Currency",
value: user?.currency ?? "",
},
dropShipping: Boolean(user?.isDropShipping),
language: {
label: "Language",
value: user?.language ?? "",
},
vatId: {
label: "VAT ID",
value: user?.vatId ?? "",
},
},
});
}, [isLoading, user]);
useEffect(() => {
if (!scriptReady) return;
void syncUser();
}, [scriptReady, syncUser]);
useEffect(() => {
if (!scriptReady || !window.Convor) return;
const query = searchParams.toString();
void window.Convor.setPage(query ? `${pathname}?${query}` : pathname);
}, [pathname, scriptReady, searchParams]);
return (
<Script
src="https://cdn.convor.io/widget.js"
data-key={process.env.NEXT_PUBLIC_CONVOR_KEY}
strategy="lazyOnload"
onReady={() => setScriptReady(true)}
/>
);
}For authenticated identity, fetch userHash from your own server after
Convor.ready() provides visitorId, then call identify() instead of relying
only on setUser().
Ostatnia aktualizacja: 10 sie 2026
Czy ta strona była pomocna?