Skip to main content

Headless Chat SDK

The Yellow Chat SDK (@yellowdotai/yellow-chat-sdk) is a TypeScript SDK for integrating Yellow.ai chat into your own web application with a completely custom UI. It provides an event-based communication layer, giving you full separation between the data layer (handled by the SDK) and your UI implementation.

Use it when you want your own look-and-feel instead of the standard embedded chat widget, while still using Yellow.ai's backend for bot and live-agent conversations.

Installation

npm install @yellowdotai/yellow-chat-sdk

Or with yarn:

yarn add @yellowdotai/yellow-chat-sdk

Quick start

import { YellowChat } from "@yellowdotai/yellow-chat-sdk";

const sdk = new YellowChat();

// Subscribe to events BEFORE connecting
sdk.on("connection:connected", ({ userId }) => {
console.log("Connected as:", userId);
});

sdk.on("message:received", (message) => {
// message = { id, timestamp, type, content, sender }
console.log("New message:", message);
});

// Initialize and connect
await sdk.init({
bot: "your-bot-id",
host: "https://cloud.yellow.ai",
});

await sdk.connect();

await sdk.sendMessage("Hello!");

Always subscribe to events before calling connect(), otherwise you may miss early messages.

Configuration

Pass configuration to sdk.init():

interface SDKConfig {
// Required
bot: string; // Your bot ID (e.g. 'x1632218421575')

// Server
host?: string; // API host URL (default: 'https://cloud.yellow.ai')

// Message source (important for live agent integrations - see below)
source?: string; // 'yellowmessenger' (default) or 'syncApi'

// User
userId?: string; // Pre-set user ID (auto-generated if omitted)
name?: string; // User display name

// Authentication
ymAuthenticationToken?: string; // Auth token for verified users (if required by your bot)

// Custom payload / journeys
payload?: Record<string, unknown>;
triggerJourney?: string;

// UTM parameters (analytics)
utmSource?: string;
utmCampaign?: string;
utmMedium?: string;

// Debug
debug?: boolean; // Enable debug logging (default: false)
}

How to find your host value

The host must exactly match the one shown in your bot's Chat Widget deployment script:

  1. Log in to cloud.yellow.ai.
  2. Go to Extensions → Chat Widget → Deploy.
  3. Copy the host value from the deployment script (e.g. https://cloud.yellow.ai).

Using a different host than the one in the deployment script can cause connectivity or authentication issues.

Source configuration

The source parameter determines how messages are routed through Yellow.ai's backend:

SourceUse case
yellowmessengerDefault. Fully headless, SDK-only integrations where the SDK handles all communication (send + receive) over the widget/WebSocket channel.
syncApiHybrid mode — when you send bot messages via the /integrations/sync/v1/message REST API and switch to the SDK when a live agent connects.
// Fully headless - SDK only (default)
await sdk.init({ bot: "your-bot-id", source: "yellowmessenger" });

// Hybrid mode - REST API + SDK for live agent
await sdk.init({ bot: "your-bot-id", source: "syncApi" });

Important: In hybrid mode you must set source: 'syncApi' and connect with the same user id you used as sender in the REST API calls, so the SDK stays on the same ticket. Mismatched sources/ids cause live-agent tickets to close or messages to be lost.

Live agent (agent inbox): use yellowmessenger

If your custom UI needs to receive live-agent messages (agent inbox), use source: 'yellowmessenger' and send all messages through the SDK (sdk.sendMessage(...)). This keeps sending and receiving on the same widget/WebSocket channel, which is how the backend routes agent replies — and ticket lifecycle events like ticket:resolved — back to your widget over XMPP.

await sdk.init({ bot: "your-bot-id", source: "yellowmessenger" });
await sdk.connect(userId);
await sdk.sendMessage("Hi"); // agent replies arrive on 'message:received'

Why not syncApi for agent chat? The sync API (/integrations/sync/v1/message) is a request/response channel — the agent's reply comes later, asynchronously, and there is no open channel on the sync side to push it back. So in syncApi mode, agent → user messages are not guaranteed to reach the SDK. They arrive only if (a) the same user id is used for the sync sender and sdk.connect(userId), and (b) the platform is configured to route that bot's agent messages to XMPP. If you must keep sending via the sync API, either arrange that backend routing or poll conversation history to fetch agent replies. For a purely headless widget that must show agent messages, prefer yellowmessenger.

Domain check (accepted domains)

If the current site's domain is not in the bot's accepted domains, the SDK throws a YellowChatError with code DOMAIN_NOT_ALLOWED and does not open the WebSocket. Configure allowed domains at cloud.yellow.ai → Extensions → Chat Widget → Secure Bot → Web.

sdk.on("connection:error", (error) => {
if (error.code === "DOMAIN_NOT_ALLOWED") {
console.error("This site is not allowed to use this bot:", error.message);
}
});

API reference

Lifecycle

await sdk.init(config); // Initialize (must be called first)
await sdk.connect(userId?); // Connect (userId optional)
sdk.disconnect(); // Disconnect

connect() resolves the user id in this order: the provided userId → a stored UID from localStorage → an anonymous, server-assigned UID.

Messaging

// Send a text message
await sdk.sendMessage("Hello!", { title?, metadata?, skipRateLimit? });

Returns a MessageResponse with the message id, trace id, and timestamp.

File upload

sendFile(file: File): Promise<FileUploadResponse>

Uploads a File and posts it into the conversation in a single call — the SDK uploads the file to the server and then delivers the file message into the conversation, so both the bot/agent and your own UI receive it. Works in both bot and live-agent conversations.

const input = document.querySelector('input[type="file"]');

input.addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) return;

try {
const result = await sdk.sendFile(file); // { url, name, size }
console.log("Uploaded:", result.url);
} catch (error) {
console.error("Upload failed:", error);
}
});

// Track status via events
sdk.on("file:uploadStarted", ({ fileId, fileName }) => showSpinner(fileName));
sdk.on("file:uploadCompleted", ({ fileId, file }) => showPreview(file.url));
sdk.on("file:uploadFailed", ({ fileId, error }) => showError(error.message));
interface FileUploadResponse {
url: string; // Hosted URL of the uploaded file
name: string; // Original file name
size: number; // File size in bytes
}

Files are uploaded as multipart/form-data; do not set a Content-Type header manually (the SDK does this). Failures throw a YellowChatError with code FILE_UPLOAD_FAILED or FILE_TOO_LARGE.

State queries

sdk.isConnected(); // boolean
sdk.getUserId(); // string | null
sdk.getBotInfo(); // BotInfo | null (name, icon, ...)

Events

const unsubscribe = sdk.on("message:received", (message) => { /* ... */ });
unsubscribe(); // stop listening
sdk.off("message:received", handler); // or unsubscribe by reference

Events reference

Connection events

EventPayloadDescription
connection:connectingConnection attempt started
connection:connected{ userId }Successfully connected
connection:disconnectedDisconnectReasonDisconnected
connection:reconnecting{ attempt, delay }Attempting to reconnect
connection:reconnected{ userId }Successfully reconnected
connection:reconnectFailedAll reconnection attempts failed
connection:errorSDKErrorConnection error

Message events

EventPayloadDescription
message:receivedIncomingMessageNew message received
message:sent{ id, message }Message sent successfully
message:failed{ messageId, error }Message send failed
message:typingbooleanTyping indicator status

Agent events

EventPayloadDescription
agent:assignedAgentProfileLive agent assigned to the conversation
agent:leftAgent left or ticket closed/resolved
agent:typingbooleanAgent is typing

Ticket events

EventPayloadDescription
ticket:created{ ticketId }Support ticket created
ticket:closedTicket/conversation closed
ticket:resolvedTicket resolved
ticket:transferred{ newAgent }Ticket transferred to a new agent

File events

EventPayloadDescription
file:uploadStarted{ fileId, fileName }File upload started
file:uploadProgress{ fileId, progress }Upload progress (0–100), if supported
file:uploadCompleted{ fileId, file }Upload completed (file = { url, name, size })
file:uploadFailed{ fileId, error }Upload failed

History & network events

EventPayloadDescription
history:previousMessagesIncomingMessage[]Previous messages (auto-fetched on connect; newest first)
network:onlineNetwork connection restored
network:offlineNetwork connection lost

Message types

All messages from the SDK use a standardized, flattened format — internal metadata is stripped, so you only receive what your UI needs:

interface IncomingMessage {
id: string;
timestamp: Date;
type: MessageType; // 'text' | 'image' | 'video' | 'audio' | 'file' | 'quickReplies' | 'cards' | 'location' | 'datePicker' | 'feedback' | 'filePrompt' | 'system'
content: MessageContent;
sender: "bot" | "agent" | "user" | "system";
}

Content by type:

// text / system : { message: string }
// image / audio : { url: string }
// video : { url, controls, autoplay, loop, muted, downloadable, thumbnail }
// file : { url, name, size?, mimeType? }
// quickReplies : { quickReplies: { title?, options[] } }
// cards : { cards: Card[] }
// location : { title?, options[], showMap, mapsApiKey } | { lat, long }
// datePicker : { display: 'date' | 'dateTime' | 'time' | 'month' | 'range' }
// feedback : { title, surveyType, detailedFeedback?, ... }
// filePrompt : { message, allowMultiple, allowSkip, safeguardUploadedFile }

System messages are generated automatically for: agent joined ("[Agent] joined the conversation"), ticket closed ("Conversation ended"), and ticket resolved ("Ticket has been resolved").

Handling messages

sdk.on("message:received", (message) => {
switch (message.type) {
case "text":
renderText(message.content.message, message.sender);
break;
case "image":
renderImage(message.content.url);
break;
case "file":
renderFileDownload(message.content.url, message.content.name);
break;
case "quickReplies":
renderQuickReplies(message.content.quickReplies);
break;
case "system":
renderSystemMessage(message.content.message);
break;
// ... handle remaining types
}
});

Error handling

import { YellowChatError } from "@yellowdotai/yellow-chat-sdk";

try {
await sdk.sendMessage("Hello!");
} catch (error) {
if (error instanceof YellowChatError) {
console.error(error.code, error.message);
}
}

sdk.on("connection:error", (error) => console.error(error.code, error.message));
sdk.on("message:failed", ({ messageId, error }) => markFailed(messageId, error));

Common error codes: CONNECTION_FAILED, CONNECTION_TIMEOUT, AUTH_FAILED, MESSAGE_SEND_FAILED, MESSAGE_RATE_LIMITED, FILE_UPLOAD_FAILED, FILE_TOO_LARGE, BOT_LOAD_FAILED, DOMAIN_NOT_ALLOWED, NETWORK_ERROR, INVALID_CONFIG.

Best practices

  1. Subscribe to events before connect() so you don't miss early messages.
  2. Handle reconnection via connection:reconnecting / connection:reconnected — the SDK auto-reconnects and re-fetches missed history.
  3. Check isConnected() before sending.
  4. Clean up on unmount: call the unsubscribe functions returned by sdk.on(...) and sdk.disconnect().

Troubleshooting

  • Connection fails immediately — verify the bot id, that host matches the deployment script, and that your domain is in the bot's accepted domains.
  • DOMAIN_NOT_ALLOWED — add your site's domain under Extensions → Chat Widget → Secure Bot → Web.
  • CORS errors on a customer domain — ensure you're using the latest SDK version; the SDK identifies the bot via the ?bot= query param and avoids custom headers that some gateway routes don't allow.

Support

For issues and feature requests, contact Yellow.ai support.