# Web Core API

> Build a custom chat UI on KustomerCore, the headless web chat SDK. Loading core.js, init, conversations, messages, typing, read state, knowledge base, CSAT, identity, and every event.

Source: https://developer.kustomer.com/docs/chat/web-core-api

`core.js` is the chat SDK without a UI. It handles identity, conversations, messages, realtime delivery and the knowledge base, and you render everything yourself. The widget is built on the same Core, so anything the widget does, your UI can do too.

> **Widget or Core?:** Use the [widget](https://developer.kustomer.com/docs/chat/web) unless you need a chat UI that's part of your own design system, such as chat embedded in a page rather than floating, or a fully custom message renderer. With Core you own accessibility, attachments, offline states and everything else the widget gives you for free.

## Load Core

```html title="index.html"
<script
  src="https://cdn.kustomerapp.com/chat-web/core.js"
  data-kustomer-api-key="YOUR_CHAT_KEY"
></script>
<script>
  window.addEventListener('kustomerCoreLoaded', function () {
    KustomerCore.init({ brandId: 'YOUR_BRAND_ID' }, function (res, error) {
      if (error) return console.error(error)
      console.log('Chat settings', res.settings)
    })
  })
</script>
```

Use a chat key with only the `org.tracking` role, as for the widget. Core runs in a hidden iframe, defines `window.KustomerCore`, and fires `kustomerCoreLoaded` when it's ready. `init` calls made before that are queued.

Every method takes an optional callback, called as `callback(response, error)`. Check `error` first: it's an `Error` with optional `status`, `code`, `title` and `source` from the API.

## init

**KustomerCore.init(options, callback)**

The response's `settings` is what admins configured for the brand in Kustomer. Use it to render your UI:

| Setting                                                  | Meaning                                                      |
| -------------------------------------------------------- | ------------------------------------------------------------ |
| `teamName`, `teamIconUrl`                                | Who the customer is talking to                               |
| `greeting`, `waitMessage`                                | Text for the empty state and while waiting for an agent      |
| `colors`                                                 | The brand's chat colors                                      |
| `widgetType`                                             | Whether the brand uses chat, the knowledge base, or both     |
| `noHistory`, `singleSessionChat`                         | Hide past conversations, or allow only one open conversation |
| `disableAttachments`, `closableChat`                     | Whether customers can attach files or end conversations      |
| `offhoursDisplay`, `offhoursMessage`, `offhoursImageUrl` | What to show outside business hours                          |
| `activeAssistant`, `knowledgeBaseId`                     | The brand's assistant and knowledge base                     |
| `domainCriteria`                                         | Which page URLs should show chat. Check with `isUrlAllowed`. |

Call `getChatSettings()` later to read them again, and `isChatAvailable()` for the current availability: `{ availability: 'online' | 'offline' | 'hidden' | 'disabled' }`.

## Build a minimal chat

```js title="custom-chat.js"
let conversationId

KustomerCore.addListener('onMessageReceived', function (message) {
  if (message.conversationId !== conversationId) return
  render(message)
  KustomerCore.markRead({ conversationId })
})

KustomerCore.addListener('onAgentTypingActivity', function (activity) {
  showTyping(activity)
})

function startChat() {
  KustomerCore.createConversation({ title: 'Help with my order' }, function (conversation, error) {
    if (error) return showError(error)
    conversationId = conversation.conversationId
  })
}

function send(body) {
  KustomerCore.sendMessage({ conversationId, body }, function (message, error) {
    if (error) return showError(error)
    render(message)
  })
}

let typingTimer
function onInput() {
  KustomerCore.sendTypingActivity({ conversationId, typing: true })
  clearTimeout(typingTimer)
  typingTimer = setTimeout(function () {
    KustomerCore.sendTypingActivity({ conversationId, typing: false })
  }, 3000)
}
```

A message object looks like this:

```json title="Message"
{
  "messageId": "6716a3b2c4e5f60012ab34cd",
  "conversationId": "6716a3a9c4e5f60012ab34c1",
  "body": "Your order shipped this morning.",
  "direction": "out",
  "createdAt": "2026-10-21T15:04:12.511Z",
  "attachments": [],
  "sentBy": { "type": "user", "id": "5f1b…", "displayName": "Ada", "avatarUrl": "https://…" },
  "isInAssistantMode": false
}
```

`direction` is `in` for the customer's messages and `out` for agents, assistants and automations.

## Methods

### Conversations

| Method                                                           | Parameters                                                                                    | Response                                                                                                        |
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `createConversation(params, cb)`                                 | `title` (required), `brand`, `assistant`, `lang`, `custom`                                    | The conversation                                                                                                |
| `getConversations(params, cb)`                                   | `page`, `pageSize`, `brandId`                                                                 | `{ conversations, pages }`                                                                                      |
| `getConversation({ conversationId }, cb)`                        |                                                                                               | The conversation                                                                                                |
| `endConversation({ conversationId }, cb)`                        |                                                                                               | Ends it. Fires `onConversationEnded`.                                                                           |
| `describeConversation({ conversationId, customAttributes }, cb)` |                                                                                               | `{ conversationId, custom }`                                                                                    |
| `handoff(params, cb)`                                            | `active`, `from`, `messages` (at least one `in` and one `out`), `brand`, `externalId`, `lang` | `{ conversationId, ended, createdAt }`. Imports a conversation that started elsewhere, such as in your own bot. |

A conversation has `conversationId`, `createdAt`, `lastMessageAt`, `preview`, `lastResponder` (`displayName`, `avatarUrl`), `ended`, `isInAssistantMode` and `custom`.

### Messages

| Method                                           | Parameters                                                                      | Response                                                            |
| ------------------------------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `sendMessage(params, cb)`                        | `conversationId`, `body`, `attachments` (`[{ file: File }]`), `payload`, `lang` | The sent message                                                    |
| `getMessages({ conversationId }, cb)`            |                                                                                 | `{ conversationId, messages, satisfaction }`                        |
| `getHistoricalMessages(params, cb)`              | `conversationId`, `count`, `before`                                             | Older messages, for scrolling back                                  |
| `markRead({ conversationId })`                   |                                                                                 | Marks every unread message in the conversation read, including CSAT |
| `getUnreadCount()`                               |                                                                                 | `{ total, conversations }` (returned, not a callback)               |
| `sendTypingActivity({ conversationId, typing })` |                                                                                 | Shows or hides the customer's typing indicator to agents            |
| `sendPresenceActivity({ presence })`             | `'online'` or `'offline'`                                                       | Tells agents whether the customer is on the page                    |

Attachments are browser `File` objects, for example from `<input type="file">`. Respect `settings.disableAttachments`.

### Identity

| Method                                        | Parameters                                                                         | Response                                        |
| --------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------- |
| `login({ jwtToken }, cb)`                     | A JWT signed by your server                                                        | `{ identified, customerId, email, externalId }` |
| `isLoggedIn({ email } \| { externalId }, cb)` |                                                                                    | `true` or `false`                               |
| `logout(cb)`                                  |                                                                                    | Clears the session from the browser             |
| `describeCustomer(params, cb)`                | `attributes` (`sharedEmails`, `sharedPhones`, `sharedSocials`), `customAttributes` | `{ customerId, custom, sharedEmails, … }`       |

See [Customer identity](https://developer.kustomer.com/docs/chat/identity) for generating the JWT.

### Knowledge base

| Method                                                                    | Parameters                                              | Response                            |
| ------------------------------------------------------------------------- | ------------------------------------------------------- | ----------------------------------- |
| `getKnowledgeBaseConfig(cb)`                                              |                                                         | The brand's knowledge base settings |
| `getCategories(params, cb)`                                               | `lang`, `knowledgeBaseId`                               | `{ rootCategory, categories }`      |
| `getArticles(params, cb)`                                                 | `categoryId`, `lang`, `knowledgeBaseId`                 | `{ articles }`                      |
| `getFeaturedArticles(params, cb)`                                         | `lang`, `knowledgeBaseId`                               | Featured articles                   |
| `getArticleById(params, cb)`                                              | `articleId`, `lang`, `knowledgeBaseId`                  | The article                         |
| `getArticleBySlug(params, cb)`                                            | `articleSlug`, `articleHash`, `lang`, `knowledgeBaseId` | The article                         |
| `searchArticles(params, cb)`                                              | `term`, `lang`, `knowledgeBaseId`                       | Matching articles                   |
| `submitArticleFeedback(params, cb)` / `updateArticleFeedback(params, cb)` | Rating and written feedback                             | The feedback                        |

Articles have `articleId`, `title`, `description`, `htmlBody`, `slug`, `lang`, `updatedAt` and `kbUrl`. `htmlBody` is HTML from your knowledge base: sanitize it before inserting it into your page.

### Satisfaction (CSAT)

| Method                                      | Parameters                                                   |
| ------------------------------------------- | ------------------------------------------------------------ |
| `getTranslatedSatisfactionForm(params, cb)` | The CSAT form in the customer's language                     |
| `submitSatisfactionForm(params, cb)`        | `satisfactionId`, `rating`, `answers`, `submittedAt`, `lang` |

When Kustomer sends a CSAT survey for a chat conversation, Core fires `onSatisfactionReceived` with it. `getMessages` also returns it as `satisfaction`.

### Other

| Method                  | Purpose                                                                          |
| ----------------------- | -------------------------------------------------------------------------------- |
| `isUrlAllowed({ url })` | `{ allowed }`: whether chat should show on that URL under the brand's page rules |
| `getChatSettings()`     | The settings from `init`                                                         |
| `isChatAvailable()`     | Current availability                                                             |
| `stop()`                | Shut Core down                                                                   |

## Events

Subscribe with `KustomerCore.addListener(event, callback)` and unsubscribe with `removeListener(event, callback)`, passing the same function.

| Event                              | Fires when                                                                |
| ---------------------------------- | ------------------------------------------------------------------------- |
| `onConversationCreate`             | A conversation is created                                                 |
| `onMessageReceived`                | A message arrives from an agent, assistant or automation                  |
| `onMessageSent`                    | The customer's message is sent                                            |
| `onUnreadCountChange`              | Unread counts change                                                      |
| `onAgentTypingActivity`            | An agent starts or stops typing                                           |
| `onConversationEnded`              | A conversation ends                                                       |
| `onConversationUnended`            | An ended conversation is reopened                                         |
| `onAssistantEnded`                 | The assistant finishes and the conversation moves on, usually to an agent |
| `onSatisfactionReceived`           | A CSAT survey arrives                                                     |
| `onConversationMerged`             | Two conversations are merged in Kustomer                                  |
| `onConversationDeleted`            | A conversation is deleted in Kustomer                                     |
| `onCustomerDelete`                 | The customer is deleted in Kustomer                                       |
| `onSessionTitleChanged`            | A conversation's title changes                                            |
| `onSessionCustomAttributesUpdated` | A conversation's custom attributes change                                 |
| `onSessionOpened`                  | A conversation is opened                                                  |
| `onUrlPreviewReceived`             | A link preview is ready for a message                                     |
| `onLogin` / `onLogout`             | Identity changes                                                          |

> **For agents building on Core:** Treat `onMessageReceived` as the source of truth for new messages and `getMessages` as the source for history. Key messages by `messageId` so a message delivered by both paths renders once.
