Chat Module
Access via sdk.chat
The Chat module enables real-time messaging between your application and Wonderful agents. Sessions are established over WebSocket for low-latency, bidirectional communication.
Starting a Session
The target agent must have Omni-Channel enabled in the Wonderful Platform. This requirement is temporary and will be removed in a future release.
start(params): Promise<ChatSession>
Start a new chat session.
const session = await sdk.chat.start({
from: 'user-123', // REQUIRED - sender identifier
fromType: 'phone', // REQUIRED - 'phone' | 'email' | 'name'
agentId: 'agent-uuid', // Optional - target agent
to: '+1234567890', // Optional - recipient identifier
toType: 'phone', // Optional - 'phone' | 'email' | 'name'
locale: 'en-US', // Optional - defaults to 'en-US'
metadata: { // Optional - custom data attached to the session
name: 'John Doe',
gender: 'male',
platform: 'app',
customer_id: 'cust-123',
}
});
// Returns ChatSession with:
// - sessionId: unique identifier (= Communication ID in backend)
// - status: 'in-progress'
// - messages: [] (empty for new session)
// - connectionState: 'connected'
Standard metadata keys:
| Key | Type | Description |
|---|
name | string | User’s display name |
gender | 'male' | 'female' | 'other' | User’s gender |
platform | 'web' | 'app' | Client platform type |
You can also include any custom key-value pairs in metadata.
Understanding the from Field
The from field is the primary customer identifier displayed in the Wonderful Platform:
- Activities Tab: Shows as the main identifier for each conversation
- Communication Record: Stored as the customer’s identity
- Session Lookup: Used to associate conversations with customers
Best practices:
- Use a consistent identifier per customer (e.g., user ID, phone, email)
- The value should be meaningful to your support team
- Phone numbers should include country code:
'+1234567890'
Identity Types
type IdentityType = 'phone' | 'email' | 'name';
| Type | When to Use | Storage | Examples |
|---|
'phone' | Customer identified by phone | Stored in from_number | '+1234567890' |
'email' | Customer identified by email | Stored in from_email | 'user@example.com' |
'name' | Custom identifier/display name | Display only (not stored) | 'user-123', 'John Doe' |
Use 'phone' or 'email' when you need the identifier stored for filtering/lookup. Use 'name' for custom IDs.
Recovering a Session
recover(sessionId): Promise<ChatSession>
Recover an existing session (e.g., after app restart or page refresh). Loads previous messages.
const session = await sdk.chat.recover('session-uuid');
// Returns ChatSession with:
// - sessionId: the recovered session ID
// - status: 'in-progress' or 'completed' or 'failed'
// - messages: array of previous messages
// - connectionState: 'connected' (if active)
console.log(session.messages); // Previous messages are loaded
if (session.status === 'in-progress') {
await session.send({ type: 'text', text: "I'm back!" });
}
Recovery Edge Cases
| Scenario | Behavior |
|---|
| Session doesn’t exist | Throws SESSION_NOT_FOUND |
| Session already completed | Returns session with status: 'completed' |
| Participant data missing | Uses 'name' as default fromType |
| Multiple recovery calls | Safe — returns same session state |
try {
const session = await sdk.chat.recover('session-123');
if (session.status === 'completed') {
// Session already ended, show history only
}
} catch (error) {
if (error.code === 'SESSION_NOT_FOUND') {
// Start new session instead
}
}
ChatSession
Returned from start() or recover().
Properties
interface ChatSession {
// Properties
readonly sessionId: string; // = Communication ID in backend
readonly agentId?: string; // Agent ID (if available)
readonly status: SessionStatus;
readonly connectionState: ConnectionState;
readonly createdAt: string; // ISO 8601
readonly participant: SessionParticipant;
readonly messages: Message[];
// Methods
send(message: ChatMessage): Promise<SendResponse>;
end(): Promise<void>;
forward(destination: string): Promise<void>;
onEvent(callback: (event: SessionEvent) => void): () => void;
}
type SessionStatus = 'in-progress' | 'completed' | 'failed';
type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
interface SessionParticipant {
from: string;
fromType: IdentityType;
to?: string;
toType?: IdentityType;
}
Sending Messages
send(message): Promise<SendResponse>
Send a message to the agent. Supports text, images, and documents.
// Text message
const response = await session.send({
type: 'text',
text: 'Hello, I need help with my order'
});
// Returns: { eventId: 'uuid', timestamp: '2024-02-02T13:00:00.000Z' }
// Text with custom metadata
await session.send({
type: 'text',
text: 'Order #12345',
metadata: { order_id: '12345', priority: 'high' }
});
// Image (provide URL to hosted image)
await session.send({
type: 'image',
mediaUrl: 'https://example.com/screenshot.png',
metadata: { context: 'error_screenshot' }
});
// Document/File (provide URL to hosted file)
await session.send({
type: 'document',
mediaUrl: 'https://example.com/invoice.pdf',
fileName: 'invoice.pdf',
metadata: { document_type: 'invoice' }
});
ChatMessage
interface ChatMessage {
type: 'text' | 'image' | 'document';
text?: string;
mediaUrl?: string;
fileName?: string;
metadata?: Record<string, unknown>;
}
SendResponse
interface SendResponse {
eventId: string;
timestamp: string; // ISO 8601 format: '2024-02-02T13:00:00.000Z'
}
Validation Rules
- Text messages:
text field is required
- Image/Document messages:
mediaUrl field is required
// Valid
await session.send({ type: 'text', text: 'Hello' });
await session.send({ type: 'image', mediaUrl: 'https://...' });
// Throws INVALID_REQUEST
await session.send({ type: 'text' }); // Missing text
await session.send({ type: 'image' }); // Missing mediaUrl
Session Control
end(): Promise<void>
End the session gracefully.
await session.end();
// Session status changes to 'completed'
// Triggers 'ended' event with endedBy: 'user'
forward(destination): Promise<void>
Forward the session to another destination (team, queue, or agent).
await session.forward('support-team');
// Triggers 'ended' event with reason: 'forwarded'
| Parameter | Type | Description |
|---|
destination | string | Target identifier (team name, queue ID, or agent ID) |
Available destinations depend on your Wonderful Platform configuration. Contact support for setup.
Real-time Events
onEvent(callback): () => void
Subscribe to real-time session events via WebSocket. Returns an unsubscribe function.
The SDK maintains a WebSocket connection to receive events in real-time. Connection state events help you handle network issues gracefully.
const unsubscribe = session.onEvent((event) => {
switch (event.type) {
// New message received (from agent or system)
case 'message':
handleMessage(event.data);
break;
// WebSocket connected successfully
case 'connected':
console.log('Connected!');
break;
// WebSocket disconnected (network issue, server restart, etc.)
case 'disconnected':
// willRetry: true means SDK will auto-reconnect
if (event.data.willRetry) {
showReconnectingUI();
} else {
showConnectionLostUI();
}
break;
// Reconnection attempt in progress
case 'reconnecting':
showNotification(
`Reconnecting... (attempt ${event.data.attempt}/${event.data.maxAttempts})`
);
break;
// WebSocket reconnected after disconnection
case 'reconnected':
hideReconnectingUI();
// missedMessages: number of messages received while disconnected
break;
// Error occurred
case 'error':
console.error(event.data.message);
if (event.data.code === 'PII_MASKING_ENABLED') {
showPiiMaskingWarning();
}
break;
// Session ended (by user, agent, or system)
case 'ended':
console.log('Session ended by:', event.data.endedBy);
navigateAway();
break;
}
});
// Later: stop listening and close WebSocket
unsubscribe();
Event Types
type SessionEvent =
| MessageEvent
| ConnectedEvent
| DisconnectedEvent
| ReconnectingEvent
| ReconnectedEvent
| ErrorEvent
| SessionEndedEvent;
| Event Type | Data | Description |
|---|
message | Message | New message received |
connected | { sessionId, connectedAt } | WebSocket connected |
disconnected | { reason, willRetry } | WebSocket disconnected |
reconnecting | { attempt, maxAttempts } | Reconnection in progress |
reconnected | { reconnectedAt, missedMessages } | Successfully reconnected |
ended | { reason, endedBy } | Session ended |
error | { code, message, recoverable } | Error occurred |
Event Type Definitions
interface MessageEvent {
type: 'message';
data: Message;
}
interface ConnectedEvent {
type: 'connected';
data: { sessionId: string; connectedAt: string }; // ISO 8601
}
interface DisconnectedEvent {
type: 'disconnected';
data: {
reason: 'network' | 'timeout' | 'server' | 'manual';
willRetry: boolean; // true = SDK will auto-reconnect
};
}
interface ReconnectingEvent {
type: 'reconnecting';
data: {
attempt: number; // Current attempt (1, 2, 3...)
maxAttempts: number; // Maximum attempts before giving up
};
}
interface ReconnectedEvent {
type: 'reconnected';
data: {
reconnectedAt: string; // ISO 8601
missedMessages: number; // Messages received while disconnected
};
}
interface ErrorEvent {
type: 'error';
data: { code: string; message: string; recoverable: boolean };
}
interface SessionEndedEvent {
type: 'ended';
data: {
reason: 'completed' | 'forwarded' | 'error';
endedBy?: 'user' | 'agent' | 'system';
};
}
Messages
Message Type
Represents a message received from the backend (via WebSocket or recovery).
interface Message {
/** Unique message identifier */
eventId: string;
/** Session/communication ID */
sessionId: string;
/** Who sent the message */
speaker: 'user' | 'agent' | 'system' | 'human_agent';
/** Language/locale code */
locale?: string;
/** Message content */
data: MessageData;
/** Custom metadata attached to this message */
metadata?: Record<string, unknown>;
/** When the message was sent (ISO 8601) */
timestamp?: string;
}
interface MessageData {
type: MessageDataType;
text?: string;
mediaUrl?: string;
fileName?: string;
action?: MessageAction;
latitude?: number;
longitude?: number;
locationName?: string;
locationAddress?: string;
}
type MessageDataType =
| 'text' // Text content
| 'image' // Image with mediaUrl
| 'document' // Document with mediaUrl and fileName
| 'voice' // Voice message (received only)
| 'audio' // Audio file (received only)
| 'location' // Location data (received only)
| 'action'; // System action (forward, end, hangup)
interface MessageAction {
type: 'forward' | 'end' | 'hangup';
destination?: string; // For 'forward': target destination
metadata?: Record<string, unknown>;
}
Speaker Types
| Speaker | Description | When Used |
|---|
'user' | The end customer using your app | Messages sent via session.send() |
'agent' | The AI agent | Responses from the Wonderful agent |
'system' | System-generated messages | Notifications, status updates |
'human_agent' | Human support agent | When session is escalated to human |
The SDK uses 'user' while the backend uses 'customer'. This mapping is handled automatically.
session.onEvent((event) => {
if (event.type === 'message') {
const { data } = event.data;
switch (data.type) {
case 'text':
displayText(data.text);
break;
case 'image':
displayImage(data.mediaUrl);
break;
case 'document':
downloadFile(data.mediaUrl, data.fileName);
break;
case 'voice':
case 'audio':
playAudio(data.mediaUrl);
break;
case 'location':
showOnMap(data.latitude, data.longitude, data.locationName);
break;
}
}
});
The SDK supports two types of metadata: session metadata and message metadata.
Attached to the entire conversation when starting a session. Useful for tracking context like user info, source app, or business data.
const session = await sdk.chat.start({
from: 'user-123',
fromType: 'phone',
metadata: {
customer_id: 'cust-456',
subscription_plan: 'premium',
source: 'mobile-app',
app_version: '2.1.0'
}
});
Session metadata is sent to the backend merged with participant info:
{
"metadata": {
"from": "user-123",
"from_type": "phone",
"customer_id": "cust-456",
"subscription_plan": "premium",
"source": "mobile-app",
"app_version": "2.1.0"
}
}
Attached to individual messages. Useful for tracking intent, context, or business data per message.
// Sending with metadata
await session.send({
type: 'text',
text: 'I need help with order #12345',
metadata: {
order_id: '12345',
intent: 'order_inquiry',
priority: 'high'
}
});
// Receiving metadata
session.onEvent((event) => {
if (event.type === 'message') {
const msg = event.data;
console.log('Message:', msg.data.text);
console.log('Metadata:', msg.metadata);
}
});
| Type | Stored With | Available On Recovery |
|---|
| Session metadata | Communication record | Yes (via backend API) |
| Message metadata | Each transcription | Yes (session.messages[].metadata) |
Best practices:
- Use session metadata for user/context info that applies to the whole conversation
- Use message metadata for per-message context (intent, attachments, actions)
- Keep metadata JSON-serializable (no functions, circular references)
- Avoid storing sensitive data (PII) in metadata unless required