---
title: Exotel Chat SDK — Integration Reference
slug: contact-center/exotel-chat-sdk-integration-reference
createdAt: 2026-05-11T11:08:38.563Z
---

# Overview

The **Exotel Chat SDK** (`exotel-chat-sdk.js`) is a JavaScript library that embeds the Exotel Live Chat widget into any web page. It exposes a global `window.exochat` object with methods for starting chat sessions, sending and receiving messages, handling file uploads, read receipts, and CSAT feedback.

This reference is intended for **third-party chatbot and web application developers** who need to integrate with the Exotel Chat widget — for example, to hand off bot sessions to live agents, exchange messages programmatically, or embed the widget in a custom CRM or portal.

# How It Works

1. A web page or chatbot application includes `exotel-chat-sdk.js` via a `<script>` tag.
2. On `DOMContentLoaded`, the host page calls `exochat.initialize(config)` with the Exotel tenant credentials and campaign settings.
3. When a customer (or bot) is ready to chat, the host calls `exochat.startChat(params)`. This call:
   - POSTs to the Exotel Chat REST API to provision an XMPP user and MUC room for the session.
   - Dynamically loads **Converse.js** (XMPP client library) and connects over BOSH.
   - Joins the provisioned MUC room, where an agent (or queue) is waiting.
4. Incoming messages are received via the XMPP listener and surfaced through the `exochat.on("messageReceived", callback)` event.
5. At the end of the session, the agent closes the chat. The SDK receives a `chatEnded` message and (optionally) presents a CSAT feedback form.

***

# Prerequisites

- The Exotel Chat service must be deployed and reachable at `chat_service_domain` (default port 4440).
- The CMS service must be reachable at `cms_service_domain` (default port 4433) for feedback configuration.
- The BOSH endpoint (`converse.bosh_service_url`) must be accessible from the customer's browser.
- A valid `auth_token` (`RestrictedToken <token>`) must be provided for REST API calls.
- A live campaign with at least one configured nodeflow must exist (`campaignId`, `nodeflowId`).

***

# Embedding the SDK

```html
<!-- Include the SDK -->
<script src="https://your-host/exotel-chat-sdk.js" defer></script>
<script>
  document.addEventListener("DOMContentLoaded", () => {
    window.exochat.initialize({
      chat_service_domain: "https://omni.example.com:4440",
      cms_service_domain:  "https://omni.example.com:4433",
      converse: {
        bosh_service_url:  "https://omni.example.com:4442/http-bind/"
      },
      auth_token:                 "RestrictedToken <your_token>",
      contactCenterId:            1,
      campaignId:                 10,
      nodeflowId:                 3,
      processId:                  2,
      locale:                     "en",
      show_controlbox_by_default: true,
    });
  });
</script>
```

***

# `exochat.initialize(config)`

Initialises the SDK, merges the provided values over defaults, and injects the chat icon into the DOM. Must be called once before any other method.

**Signature:** `exochat.initialize(config: ExochatConfig): void`

## Configuration parameters

| Parameter                    | Type           | Required | Default     | Description                                                                           |
| ---------------------------- | -------------- | -------- | ----------- | ------------------------------------------------------------------------------------- |
| `chat_service_domain`        | string         | Yes      | —           | Base URL of the Exotel chat service (port 4440). E.g. `https://omni.example.com:4440` |
| `cms_service_domain`         | string         | Yes      | —           | Base URL of the CMS service (port 4433). Used for feedback config lookup.             |
| `converse.bosh_service_url`  | string         | Yes      | —           | BOSH endpoint for XMPP. E.g. `https://omni.example.com:4442/http-bind/`               |
| `auth_token`                 | string         | Yes      | —           | Authorization header value. Format: `"RestrictedToken <token>"`                       |
| `contactCenterId`            | number         | Yes      | —           | Numeric ID of the contact center.                                                     |
| `campaignId`                 | number         | Yes      | `1`         | Campaign to route the chat into.                                                      |
| `nodeflowId`                 | number         | Yes      | `5`         | Nodeflow (IVA) ID for routing logic.                                                  |
| `processId`                  | number         | Yes      | `4`         | Process ID. Used for feedback config lookup.                                          |
| `locale`                     | string         | No       | `"en"`      | UI locale for the chat widget. E.g. `"en"`, `"hi"`.                                   |
| `accountId`                  | string \| null | No       | `null`      | Optional account ID sent as a request header for multi-tenant setups.                 |
| `theme_color`                | string         | No       | `"#192D3F"` | Primary brand color for the chat widget UI.                                           |
| `show_controlbox_by_default` | boolean        | No       | `true`      | Whether to show the chat icon immediately on load.                                    |
| `view_mode`                  | string         | No       | `""`        | Display mode for the widget layout.                                                   |

***

# Methods

All methods are available on `window.exochat` after `initialize()` is called.

## Session Management

### `startChat(params)`

Creates an XMPP user and MUC room via the Exotel REST API, then loads Converse.js and joins the room. This is the primary method for bot-to-agent handoff.

**Signature:** `exochat.startChat(params: StartChatParams): Promise<Room>`

### Input parameters

| Field               | Type                 | Required | Description                                                                                                              |
| ------------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| `fullName`          | string               | Yes      | Customer display name shown to the agent.                                                                                |
| `email`             | string               | Yes      | Customer email. Stored as a user property on the session.                                                                |
| `phone`             | string               | Yes      | Customer phone number. Stored as a user property on the session.                                                         |
| `user_id`           | string \| null       | No       | Bot user ID (for bot-to-agent transfers). Setting this marks the session as bot-transferred.                             |
| `session_id`        | string \| null       | No       | Bot session ID (for bot-to-agent transfers). Passed as `botSessionId`.                                                   |
| `...additionalInfo` | Record\<string, any> | No       | Any additional key-value pairs forwarded as `additionalInfo` to the REST API (e.g. custom CRM fields, intent, language). |

### Internal REST call

**Endpoint:** `POST {chat_service_domain}/plugins/restapi/v1/xtrm/users/connection`

**Request headers:**

```json
{
  "Content-Type":    "application/json",
  "Authorization":   "<config.auth_token>",
  "contactCenterId": "<config.contactCenterId>",
  "accountId":       "<config.accountId>"
}
```

**Request body:**

```json
{
  "requestId": "uuid-v4",
  "userEntity": {
    "name": "<fullName>",
    "properties": [
      { "key": "email",      "value": "<email>" },
      { "key": "phone",      "value": "<phone>" },
      { "key": "nodeflowId", "value": "<config.nodeflowId>" },
      { "key": "locale",     "value": "<config.locale>" }
    ]
  },
  "serviceName":     "conference",
  "campaignId":      "<config.campaignId>",
  "sendInvitations": true,
  "botTransferred":  false,
  "additionalInfo":  { },
  "botSessionInfo": {
    "userId":       "<user_id | null>",
    "botSessionId": "<session_id | null>"
  }
}
```

**Successful response (200 OK):**

```json
{
  "userEntity": {
    "username": "xmpp_user@domain",
    "password": "xmpp_password",
    "name":     "Customer Display Name"
  },
  "mucRoomEntity": {
    "roomName": "room_abc123",
    "members":  ["user@domain", "agent@domain"],
    "owners":   ["agent@domain"]
  },
  "exportTranscriptAllowed": true
}
```

**Promise resolution:** On success, the Promise resolves with the Converse **Room** object returned by `joinRoom()`. On failure, it rejects with one of:

| Error shape                           | Cause                                                      |
| ------------------------------------- | ---------------------------------------------------------- |
| `{ errorData, roomJid, customerJid }` | Failed to connect to XMPP/BOSH server after REST succeeded |
| `string` (HTTP statusText)            | REST API returned a non-2xx status                         |
| `Error` object                        | Network / fetch exception                                  |

### Bot-to-agent handoff example

```javascript
exochat.startChat({
  fullName:   "Jane Smith",
  email:      "jane@example.com",
  phone:      "+919988776655",
  user_id:    "bot_user_id_123",
  session_id: "bot_session_abc",
  intent:     "billing_issue",
  language:   "en",
}).then((room) => {
  console.log("Handoff complete:", room);
}).catch((err) => {
  console.error("Handoff failed:", err.errorData || err);
});
```

***

### `logout()`

Logs the XMPP user out of the Converse session.

**Signature:** `exochat.logout(): void`

***

### `getUserStatus()`

Returns the current XMPP presence status of the user (e.g. `"online"`, `"away"`).

**Signature:** `exochat.getUserStatus(): string`

***

## Room Management

### `createRoom(roomJid, nick)`

Creates a new MUC room in Converse.

**Signature:** `exochat.createRoom(roomJid: string, nick: string): Promise<void>`

| Parameter | Type   | Description                                                   |
| --------- | ------ | ------------------------------------------------------------- |
| `roomJid` | string | Full MUC JID of the room (e.g. `room_abc@conference.domain`). |
| `nick`    | string | Nickname to use inside the room.                              |

***

### `joinRoom(roomJid, nick)`

Opens and joins an existing MUC room.

**Signature:** `exochat.joinRoom(roomJid: string, nick: string): Promise<Room>`

***

### `leaveRoom(roomJid)`

Closes (leaves) a MUC room in Converse.

**Signature:** `exochat.leaveRoom(roomJid: string): void`

***

## Messaging

### `sendMessage(roomJid, body, type, files)`

Sends a message to the specified MUC room.

**Signature:** `exochat.sendMessage(roomJid: string, body: string, type: string, files: FileAttachment[] | null): void`

| Parameter | Type                      | Description                                                                 |
| --------- | ------------------------- | --------------------------------------------------------------------------- |
| `roomJid` | string                    | Full MUC JID of the destination room.                                       |
| `body`    | string                    | Message text (or file URL when `type` is `MEDIA_MSG`).                      |
| `type`    | string                    | `"NORMAL_TEXT_MSG"` for plain text; `"MEDIA_MSG"` for file attachments.     |
| `files`   | FileAttachment\[] \| null | Array of file objects when `type` is `MEDIA_MSG`; `null` for text messages. |

```javascript
// Send a text message
exochat.sendMessage(roomJid, "Hello, how can I help?", "NORMAL_TEXT_MSG", null);

// Send a file attachment
exochat.sendMessage(roomJid, fileUrl, "MEDIA_MSG", [{ url: fileUrl, name: "report.pdf" }]);
```

***

### `receiveMessage(message)`

Triggers the `"messageReceived"` event and returns the message object. Typically called internally by the XMPP listener; can be called manually to inject messages.

**Signature:** `exochat.receiveMessage(message: MessageObject): MessageObject`

***

### `fetchArchivedMessages(roomJid)`

Fetches the message history for a room from the REST API and replays it into the widget's message list.

**Signature:** `exochat.fetchArchivedMessages(roomJid: string): Promise<void>`

**Internal REST call:** `GET {chat_service_domain}/plugins/restapi/v1/xtrm/chatrooms/{roomJid}/messages`

***

## Typing Indicators

### `sendTypingStatus(roomJid)`

Sends an XMPP "composing" chat state to the room (the local user is actively typing).

**Signature:** `exochat.sendTypingStatus(roomJid: string): void`

***

### `sendPausedStatus(roomJid)`

Sends an XMPP "paused" chat state to the room (the local user has stopped typing).

**Signature:** `exochat.sendPausedStatus(roomJid: string): void`

***

## Read Receipts

### `sendDisplayedReceipt(roomJid, msgid)`

Sends an XEP-0333 "displayed" marker acknowledging that the customer has read a message. Also triggers the `"displayedReceipt"` event.

**Signature:** `exochat.sendDisplayedReceipt(roomJid: string, msgid: string): void`

***

### `sendReceiveReceipt(roomJid, msgid)`

Sends an XEP-0184 "received" receipt confirming message delivery to the client. Also triggers the `"receiveReceipt"` event.

**Signature:** `exochat.sendReceiveReceipt(roomJid: string, msgid: string): void`

***

## UI Control

### `showConverseChat(e?)`

Shows the Converse chat panel within the widget.

**Signature:** `exochat.showConverseChat(e?: any): void`

***

### `hideConverseChat(e?)`

Hides the Converse chat panel.

**Signature:** `exochat.hideConverseChat(e?: any): void`

***

### `disconnectConverse(e?)`

Disconnects the active XMPP session (returns a Promise).

**Signature:** `exochat.disconnectConverse(e?: any): Promise<void>`

***

## File Upload (XEP-0363 HTTP Upload)

Files are uploaded through the XMPP HTTP Upload extension. Use `uploadFileToConverse()` for the simplest integration.

### `uploadFileToConverse(file)` *(recommended)*

Runs the full upload flow — discovers the upload service, requests a slot, uploads the file, and returns the public GET URL.

**Signature:** `exochat.uploadFileToConverse(file: File): Promise<string | null>`

Returns: The public URL of the uploaded file on success; `null` on failure.

```javascript
const file = fileInput.files[0];
const publicUrl = await exochat.uploadFileToConverse(file);

if (publicUrl) {
  exochat.sendMessage(roomJid, publicUrl, "MEDIA_MSG", [{ url: publicUrl, name: file.name }]);
} else {
  console.error("File upload failed");
}
```

***

### `requestSlotFromConverse(filename, size, contentType)`

Requests an HTTP upload slot from the XMPP server component (XEP-0363 IQ).

**Signature:** `exochat.requestSlotFromConverse(filename: string, size: number, contentType: string): Promise<{ putUrl: string, getUrl: string } | null>`

***

### `uploadFile(file, putUrl, getUrl)`

Uploads a File via HTTP PUT to the slot URL.

**Signature:** `exochat.uploadFile(file: File, putUrl: string, getUrl: string): Promise<string | null>`

Returns: `getUrl` on HTTP 200; `null` on error.

***

### `discoverFileUploadJID()`

Discovers the JID of the XMPP HTTP Upload service component.

**Signature:** `exochat.discoverFileUploadJID(): Promise<string | null>`

***

### `requestUploadServiceInfo(jid)`

Sends a disco#info IQ to the upload service JID.

**Signature:** `exochat.requestUploadServiceInfo(jid: string): Promise<any>`

***

## URL Utility Methods

| Method          | Signature                             | Returns | Description                                                           |
| --------------- | ------------------------------------- | ------- | --------------------------------------------------------------------- |
| `isImageURL`    | `isImageURL(url: string): boolean`    | boolean | `true` if the URL has an image extension (jpg, png, gif, webp, etc.). |
| `isAudioURL`    | `isAudioURL(url: string): boolean`    | boolean | `true` if the URL has an audio extension (mp3, ogg, wav, etc.).       |
| `isVideoURL`    | `isVideoURL(url: string): boolean`    | boolean | `true` if the URL has a video extension (mp4, webm, etc.).            |
| `isDocumentPdf` | `isDocumentPdf(url: string): boolean` | boolean | `true` if the URL ends with `.pdf`.                                   |
| `getFileName`   | `getFileName(url: string): string`    | string  | Extracts the filename from a URL path.                                |

***

## Feedback (CSAT)

### `getFeedbackConfig()`

Fetches the feedback scheme configuration for the current campaign from the CMS.

**Signature:** `exochat.getFeedbackConfig(): Promise<FeedbackConfig | null>`

**Internal REST call:** `GET {cms_service_domain}/configuration/cc-list/{contactCenterId}/process-list/{processId}/campaigns/{campaignId}/chat-feedback-schemes-configurations/{campaignId}_{nodeflowId}_webchat`

**Response shape:**

```json
{
  "isFeedbackEnabled": true,
  "openInNewTab":      false,
  "feedbackUrl":       "https://feedback.example.com/form"
}
```

***

### `submitFeedbackResponse(interactionId, chatId, feedback)`

Posts the customer's CSAT rating to the chat service.

**Signature:** `exochat.submitFeedbackResponse(interactionId: string, chatId: string, feedback: any): Promise<Response>`

**Endpoint:** `POST {chat_service_domain}/plugins/restapi/v1/xtrm/chat-feedback`

**Request headers:**

```json
{
  "Content-Type":    "application/json",
  "Authorization":   "<config.auth_token>",
  "contactCenterId": "<config.contactCenterId>",
  "accountId":       "<config.accountId>"
}
```

**Request body:**

```json
{
  "campaign_id":    "<config.campaignId>",
  "interaction_id": "<interactionId>",
  "chat_id":        "<chatId>",
  "feedback":       { }
}
```

***

## Event Subscription

### `on(eventName, callback)`

Registers a callback for a named SDK event. Multiple callbacks per event are supported.

**Signature:** `exochat.on(eventName: string, callback: Function): void`

### `triggerEvent(eventName, ...args)`

Internally used to emit events to all registered callbacks. Can also be called directly to fire events manually.

**Signature:** `exochat.triggerEvent(eventName: string, ...args: any[]): void`

***

# Events

Subscribe with `exochat.on(eventName, callback)`.

## `messageReceived`

Fired every time a message arrives in the MUC room from the XMPP server.

```javascript
exochat.on("messageReceived", (message) => {
  if (message.chatEnded) {
    // Session was closed by the agent
    const { chatId, INTERACTION_ID } = message.interaction_data || {};
    handleChatEnd(chatId, INTERACTION_ID);
    return;
  }

  if (message.subType === "MEDIA_MSG" && message.files) {
    message.files.forEach(f => renderAttachment(f.url, f.name));
    return;
  }

  if (message.sender === "agent") {
    renderAgentMessage(message.body, message.nick, message.time);
    exochat.sendDisplayedReceipt(message.from_muc, message.msgid);
  }
});
```

### Message Object schema

| Field              | Type                                                         | Description                                                                          |
| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------ |
| `id`               | string                                                       | Unique XMPP stanza element ID.                                                       |
| `msgid`            | string                                                       | Application-level message ID used for receipts.                                      |
| `body`             | string                                                       | Plain text content of the message.                                                   |
| `from`             | string                                                       | Full JID of the sender (e.g. `room@conference.domain/nick`).                         |
| `from_muc`         | string                                                       | Bare MUC JID (e.g. `room@conference.domain`).                                        |
| `from_real_jid`    | string                                                       | Real un-masked JID of the sender.                                                    |
| `to`               | string                                                       | Recipient JID.                                                                       |
| `nick`             | string                                                       | Sender's nickname inside the MUC room.                                               |
| `occupant_id`      | string                                                       | Stable occupant ID (XEP-0421).                                                       |
| `sender`           | `"agent"` \| `"me"`                                          | `"agent"` for contact-centre-side messages; `"me"` for messages sent by this client. |
| `type`             | `"groupchat"`                                                | XMPP message type.                                                                   |
| `chat_state`       | `"active"` \| `"composing"` \| `"paused"` \| `"inactive"`    | XMPP chat state of the sender.                                                       |
| `time`             | ISO 8601 string                                              | Message timestamp.                                                                   |
| `received`         | timestamp                                                    | Time the message was received locally.                                               |
| `message`          | string                                                       | Alias for `body`.                                                                    |
| `subType`          | `"NORMAL_TEXT_MSG"` \| `"MEDIA_MSG"` \| `"FORM_MSG"` \| null | Message sub-classification. `MEDIA_MSG` means file attachments are present.          |
| `files`            | FileAttachment\[] \| null                                    | Array of `{ url, name }` objects when `subType` is `MEDIA_MSG`; `null` otherwise.    |
| `userRole`         | `"AGENT"` \| `"BOT"` \| `"SUPERVISOR"` \| null               | Role of the sender on the contact-centre side.                                       |
| `is_markable`      | boolean                                                      | Whether the SDK should send a "displayed" marker back for this message.              |
| `is_marker`        | boolean                                                      | Whether this message is itself a read marker.                                        |
| `marker_id`        | string                                                       | When `is_marker` is `true`, the ID of the message being acknowledged.                |
| `receipt_id`       | string                                                       | ID for XEP-0184 receipt correlation.                                                 |
| `displayed`        | boolean                                                      | Client-side flag tracking whether "displayed" has been sent. Default: `false`.       |
| `interaction_data` | `{ chatId, INTERACTION_ID }` \| null                         | Interaction metadata. Populated when `chatEnded` is `true`.                          |
| `chatEnded`        | boolean                                                      | `true` when this message signals end-of-session (agent closed the chat).             |

***

## `displayedReceipt`

Fired when the SDK sends a "displayed" read marker for a message. Useful for updating delivery indicators in a custom UI.

```javascript
exochat.on("displayedReceipt", (roomJid, msgid) => {
  console.log("Displayed:", msgid, "in room:", roomJid);
});
```

| Argument  | Type   | Description                           |
| --------- | ------ | ------------------------------------- |
| `roomJid` | string | MUC JID where the receipt was sent.   |
| `msgid`   | string | ID of the message that was displayed. |

***

## `receiveReceipt`

Fired when the SDK sends an XEP-0184 "received" receipt confirming delivery to this client.

```javascript
exochat.on("receiveReceipt", (roomJid, msgid) => {
  console.log("Received receipt for:", msgid);
});
```

| Argument  | Type   | Description                     |
| --------- | ------ | ------------------------------- |
| `roomJid` | string | MUC JID.                        |
| `msgid`   | string | ID of the acknowledged message. |

***

# CSAT Feedback Flow

After a chat ends, the SDK automatically fetches feedback config and may display a form. To drive this flow from your own UI:

```javascript
exochat.on("messageReceived", async (msg) => {
  if (!msg.chatEnded) return;

  const { chatId, INTERACTION_ID } = msg.interaction_data || {};
  const config = await exochat.getFeedbackConfig();

  if (!config?.isFeedbackEnabled) return;

  if (config.openInNewTab) {
    window.open(config.feedbackUrl, "_blank");
  } else {
    // Show your own rating UI, then on submit:
    const rating = { score: 5, comment: "Great support!" };
    await exochat.submitFeedbackResponse(INTERACTION_ID, chatId, rating);
    console.log("Feedback submitted");
  }
});
```

***

# Limitations

- The SDK dynamically loads **Converse.js 10.1.4** from the Converse CDN (`cdn.conversejs.org`). The customer's browser must be able to reach that CDN, or the CDN must be mirrored internally.
- XMPP connectivity requires the `bosh_service_url` to be accessible from the customer's browser over HTTPS.
- The chat icon UI (`#exotel-chat-icon`) is injected into the host page's `<body>`. Custom CSS or `z-index` styles on the host page may affect its display.
- `show_controlbox_by_default: false` hides the icon on load; you must call `showConverseChat()` to show the widget programmatically.
- File upload uses XEP-0363 HTTP Upload and requires the XMPP server to have the upload service component configured.
- The `view_mode` parameter is accepted but the specific modes are not publicly documented; consult Exotel support for available values.

***

#

