SDK reference
Every method, event, error code, and limit of the KonvoAI visitor SDK for custom widgets.
The SDK is served as an ES module at https://widget.k2.konvoai.com/sdk.js and
exposes a single factory:
import { createChatClient } from "https://widget.k2.konvoai.com/sdk.js";
const chat = createChatClient({ channelId: "live_chat_YOUR_CHANNEL_ID" });createChatClient accepts:
| Option | Type | Required | Description |
|---|---|---|---|
channelId | string | yes | The channel id from the Install tab. |
The client is lazy — creating it does nothing on the network until you call
connect() or one of the REST methods.
Connection
connect(): Promise<void>
Authenticates (solving the proof-of-work challenge when there is no cached
session) and opens the realtime connection. Resolves when the connection is
open. If the attempt fails — for example the page's origin isn't allowlisted —
the promise rejects with connect_failed:<issue> (see the connection event
for the issue codes) while the SDK keeps retrying in the background.
Reconnection after drops is automatic with exponential backoff — you never
need to call connect() again; observe the connection event instead.
disconnect(): void
Closes the connection and stops reconnecting. A pending connect() promise
rejects with disconnected.
The connection event
Fires on every status change with { status, issue }:
status—"idle","connecting","open", or"closed".issue— why the connection isn't open, ornull:"limit"— the channel or organization connection cap is full; the SDK keeps retrying until a slot frees."stale"— the server reclaimed the connection slot; reconnecting."unavailable"— temporary server-side issue; retrying."rejected"— the connection was refused (usually an expired session); the SDK re-authenticates automatically."network"— the connection dropped or a heartbeat timed out.
Sending
sendMessage(input): string
const id = chat.sendMessage({
body: "Hello!", // required, ≤ 10,000 characters
attachmentIds: [uploadId], // optional, ≤ 5 per message
});Returns a client-generated message id immediately. The ack event fires with
the same id once the server has durably accepted the message — until then,
treat it as pending. Throws not_connected when the connection isn't open.
sendTyping(active: boolean): void
Visitor typing indicator. Best-effort: silently dropped while disconnected.
markDelivered(messageId) / markSeen(messageId)
Read receipts for agent messages: markDelivered when the message reached the
device, markSeen when it was actually rendered on screen. These drive the
delivery ticks agents see in the inbox.
trackPageChange({ url, title? }): void
Report a single-page-app navigation so the AI agent knows which page the visitor is on.
Receiving
Subscribe with on(event, handler); it returns an unsubscribe function.
message
An agent (human or AI) message:
{
type: "message.agent",
id: string,
streamId: string,
body: string,
html?: string, // sanitized rich-text variant
attachments?: [{ filename, mimetype, url, size? }],
showcase?: { ctaLabel, products }, // product cards, when the AI recommends products
authorKind?: "human_agent" | "ai_agent",
disclosureNotice?: string, // localized AI disclosure — display it when present
occurredAt: string, // ISO timestamp
}Attachment URLs are presigned and expire after about an hour; call
getHistory() for fresh ones.
ack
{ id, receivedAt } — the server durably accepted the visitor message with
that id.
typing
{ actorKind, displayName, active, occurredAt } — an agent started or
stopped typing.
agent
{ agentName, agentKind?, occurredAt } — the conversation's assignee changed;
agentName is null when unassigned. Use it for a chat header.
REST methods
All three authenticate automatically and transparently recover from an expired session (one retry with a freshly minted token).
getHistory(): Promise<{ streamId, messages }>
The open conversation's messages, oldest first. Each message carries author
("user" or "agent") plus the same fields as the message event.
streamId is null when the visitor has no open conversation.
uploadAttachment(file: File): Promise<upload>
Uploads a file (≤ 25 MiB) and returns
{ uploadId, filename, mimetype, size, transcript? }. Reference the
uploadId in sendMessage's attachmentIds. Audio files are transcribed
server-side; the transcript rides back on the result.
Errors: file_too_large, file_infected (failed the malware scan),
upload_limit_exceeded (too many pending uploads).
identify(email: string): Promise<void>
Binds the visitor's email to the conversation — enabling unseen-reply email notifications and cross-device conversation continuity.
Sessions and storage
The SDK stores two values in the browser:
konvoai:widget:visitor(localStorage) — the stable anonymous visitor id. Clearing it starts a fresh identity with no history.konvoai:widget:token:<channelId>(sessionStorage) — the cached session token, valid for 15 minutes. The SDK re-mints it automatically; you never handle tokens.
Errors
Methods reject (or throw) with Error objects whose message is a stable
machine code:
| Code | Meaning |
|---|---|
not_connected | sendMessage was called while the connection wasn't open. |
connect_failed:<issue> | The connect() attempt failed; the SDK keeps retrying in the background. |
challenge_failed:<status> / proof_failed:<status> | The auth handshake was rejected — commonly a non-allowlisted origin (403) or blocked country. |
pow_unsolved | The proof-of-work solve timed out (very slow device). |
file_too_large / file_infected / upload_limit_exceeded | Upload rejections — see uploadAttachment. |
history_failed:<status> / upload_failed:<status> / identify_failed:<status> | Other non-OK responses, carrying the HTTP status. |
Limits
| Limit | Value |
|---|---|
| Message body | 10,000 characters |
| Attachments per message | 5 |
| Attachment size | 25 MiB |
| Session token lifetime | 15 minutes (auto-renewed) |
| Concurrent connections | Capped per channel and per organization |