> ## Documentation Index
> Fetch the complete documentation index at: https://www.text.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Chatting as a customer

> Follow a step-by-step guide to build a functional application sending messages to chat through the Customer Chat API.

In this guide, we will create a simple web chat application from scratch using HTML, CSS, and Vanilla JavaScript to integrate with the [Customer Chat Web API](/docs/api/customer-chat).

This tutorial will help you understand what each piece of the code does and how it all fits together to enable a customer to chat with a live agent.

## When to use the Customer Chat API

Use the Customer Chat API to build a custom chat experience for customers or integrate Text's messaging into your own application. Instead of using the standard chat widget, the Customer Chat API lets you send messages as a customer via API calls.

In short, the Customer Chat API is the right tool if you need to programmatically act on behalf of a customer in a chat (sending messages, retrieving chat history).

By contrast, you would use the [Agent Chat API](/docs/api/agent-chat/) if you needed to send messages as an agent or bot. In this guide, we'll focus on the customer side.

## What this guide covers

* Using the [Customer Authorization flow](/docs/authentication/customer-tokens#cookie-grant) to obtain a customer access token using the simple Cookie grant method (ideal for web apps).
* Using the Customer Chat Web API (v3.6) to start a chat and send messages as the customer.
* Setting up the required OAuth client configuration.
* Writing the complete code for a sample application and instructions for running it locally with Node and npm.
* Show how the agent will receive the messages in Text.

## Prerequisites

Before you begin, make sure you have the following:

* [Text account](https://www.text.com/) with at least one active agent to see incoming chats. You can [start a free trial](https://accounts.text.com/signup) if you don't have an account yet.
* An OAuth client — created in Text under [Settings → API access → OAuth clients](https://www.text.com/app/settings/integrations/api-access/oauth-clients).
* [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed on your machine to set up a simple local web server and manage our project files.
* A text editor or IDE (for example, [Visual Studio Code](https://code.visualstudio.com/)) for writing code.

## Prepare an OAuth client

Before coding, create an OAuth client and copy your credentials.

Go to [Settings → API access → OAuth clients](https://www.text.com/app/settings/integrations/api-access/oauth-clients) and create a new OAuth client. Configure:

* **Display name** - a name to identify your OAuth client
* **Redirect URI whitelist** - set this to `http://localhost:8080` (where the sample app runs)
* **Client type** - select **Server-side app**

After creating, Text shows your **Client ID** and **Secret key** once — copy the Client ID. You'll also need your **organization ID**, which you can retrieve by calling [`list-organizations`](/docs/api/global-accounts/organizations/list-organizations).

For our purposes (Customer Chat API), you typically do not need to configure any custom scopes. The customer token generated via the cookie grant flow will implicitly allow chat actions for that customer on your license.

For our purposes (Customer Chat API), you typically do not need to configure any custom scopes in the app. The customer's token we'll generate will implicitly allow chat actions for that customer on your license.

Agent-side scopes like `chats--all:rw` or `customers:own` are not needed here because we are not using an agent token to impersonate customers — we will obtain a direct customer token via the cookie grant flow.

## Create a local project

Now you can set up your development environment:

1. **Clone the sample app repository**: Open and clone the sample app repository available on [GitHub](https://github.com/livechat/customer-chat-api-sample-app/).
2. **Initialize npm**: Open a terminal in the project folder and run the following command:

```bash theme={null}
npm start
```

For the app to work properly (and handle the authentication cookies), we set up a local web server in the repository (rather than opening the HTML file directly). We use a `http-server` tool to serve our files on `http://localhost:8080`.

We have already added an npm script in the package.json to automatically start the server with `npm start`:

```json theme={null}
"scripts": {
  "start": "http-server -p 8080"
}
```

You can also use other static server tools or even the Live Server extension in VSCode — any method to serve the HTML on a local URL is fine.

## Index file

You will find the `index.html` code in the sample app repository. The index file contains CSS styles, a simple chat widget with operational buttons, and a script tag to load the `app.js` application file.

## Application logic

The file `app.js` contains all the JavaScript needed to connect our front-end chat interface with the Customer Chat API. We'll explain each major function in the script, which corresponds to different features of the chat app.

**Overview of the script:** At the top of the file, we set up some configuration and state variables:

```js theme={null}
const clientId = "your_client_id";
const organizationId = "your_organization_id";
let customerToken = null;
let customerId = null;
let currentChatId = null;
let currentThreadId = null;
let isChatActive = false;
let lastEventIds = new Set();
let pollingIntervalId = null;
```

* `clientId` and `organizationId`, set to the values you copied when setting up your OAuth client (replace the placeholders with your actual IDs).
* We then declare variables like:
  * `customerToken` and `customerId` (to store the authenticated customer's token and ID),
  * `currentChatId` and `currentThreadId` (to track the ongoing chat session),
  * `isChatActive` (a boolean flag),
  * helpers like `lastEventIds` (a `Set` to keep track of message IDs we've already displayed) and `pollingIntervalId` (for managing the polling timer).

Now, let's go through the core functions one by one:

### Customer authorization

Before our customer can start chatting, we need to authorize them and get an access token. The **authorizeCustomer()** function handles this:

```js theme={null}
async function authorizeCustomer() {
  const url = "https://accounts.livechat.com/v2/customer/token";
  const payload = {
    grant_type: "cookie",
    client_id: clientId,
    organization_id: organizationId,
    response_type: "token",
    redirect_uri: window.location.origin,
  };

  try {
    const response = await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify(payload),
    });
    if (!response.ok) throw new Error(`Auth failed: ${response.status}`);
    const data = await response.json();
    customerToken = data.access_token;
    customerId = data.entity_id;
    organizationId = data.organization_id;
    console.log("✅ Customer authorized:", customerId);
    await listChats();
    if (customerToken) startPolling();
  } catch (error) {
    console.error("❌ Authorization error:", error);
  }
}
```

Here's what this does:

1. It prepares a payload for the authentication request, including our `clientId`, `organizationId`, and specifying `grant_type: "cookie"`. We use the customer authorization API endpoint (`/v2/customer/token`) to check if the user has a valid login session (cookie) and issue an access token for the Customer Chat API. The `redirect_uri` is included as part of the OAuth flow (though in our case, with `grant_type: "cookie"`, no actual redirection happens; it just uses the cookie).
2. We then make a POST request to the accounts service with `credentials: "include"`, meaning the browser will include authentication cookies for the accounts domain. Note that this requires that you are logged in to your Text account in your browser. If you are not logged in, this request will fail, so make sure you have an active session by logging into your account in the same browser before running the app.
3. If the response is successful, we parse the JSON data. It contains an access\_token (which we store in `customerToken`), and an `entity_id` (the customer's ID stored in `customerId`).
4. We log a confirmation to the console ("customer authorized") with the ID for debugging purposes.

Then we call `listChats()` to see if this customer already has an existing chat open. The `listChats()` function calls the API to get a list of chats for this customer. It looks at the first chat (if any) to determine if the customer has an existing chat.

<Warning>
  **Important:** Our APIs differentiate between **chats** and **threads**. A specific customer can have **only one active chat started** on a specific license, and this chat contains **threads**. The chats in Text are continuous — when a chat is created, it can then be deactivated and resumed. Resuming a chat starts **a new thread** inside this chat, and deactivating a chat **closes the currently open thread**.
</Warning>

If an open or pending chat exists, our `listChats` function will set `currentChatId` to that chat's ID, `currentThreadId` to the last thread in that chat, and `isChatActive` to whether that chat is still active.

This way, we can reattach to an ongoing conversation if the user refreshes the page or comes back later. If no chat is found, it means the customer has no active conversation, and we initialize our state to none (and disable the `End chat` button via `updateCloseButtonState()` accordingly).

Finally, if we did get a `customerToken`, we start the polling mechanism by calling `startPolling()` (we'll explain polling soon). This means as soon as the user is authorized, our app will begin checking for any incoming messages in case there's an active chat or if one gets started.

In summary, `authorizeCustomer()` runs automatically when the app loads (we attach it to run on `DOMContentLoaded`). It silently authenticates the user using their login cookie and prepares the app to either resume an existing chat or start a new one.

If authorization fails (for example, if the user is not logged in), an error will be logged. In a production-ready app, you could handle this by prompting the user to log in.

### Start or resume chat

Once the user is authorized, they can send a message. We have two functions to manage chat sessions: `startChat()` for beginning a brand new chat if the customer has none, and `resumeChat()` for reopening a chat that exists but has ended.

**Starting a new chat:** The `startChat(initialMessage)` function is called when the customer sends a message and `currentChatId` is not set (meaning no chat is in progress yet). It will create a new chat session with the first message:

```js theme={null}
async function startChat(initialMessage) {
  const res = await fetch(
    `https://api.livechatinc.com/v3.6/customer/action/start_chat?organization_id=${organizationId}`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${customerToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        chat: {
          thread: {
            events: [
              {
                type: "message",
                text: initialMessage,
                recipients: "all",
              },
            ],
          },
        },
      }),
    },
  );

  const data = await res.json();
  currentChatId = data.chat_id;
  currentThreadId = data.thread_id;
  isChatActive = true;
  lastEventIds.add(data.event_id);
  appendMessageToChat("You", initialMessage);
  console.log("✅ Chat started:", currentChatId);
  isChatActive = true;
  updateCloseButtonState();
}
```

**Resuming an existing chat:** The `resumeChat()` function is used if `currentChatId` exists but `isChatActive` is false (meaning the chat has ended, but we still have its ID).

```js theme={null}
async function resumeChat() {
  const res = await fetch(
    `https://api.livechatinc.com/v3.6/customer/action/resume_chat?organization_id=${organizationId}`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${customerToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ chat: { id: currentChatId } }),
    },
  );
  const data = await res.json();
  currentThreadId = data.thread_id;
  isChatActive = true;
  console.log("🔁 Chat resumed:", currentChatId);
  updateCloseButtonState();
}
```

### Send message

The `sendMessage(messageText)` function is invoked whenever the user sends a message. This function decides whether to start a new chat, resume an old chat, or simply send the message on an active chat.

```js theme={null}
async function sendMessage(messageText) {
  if (!customerToken) return;

  if (!currentChatId) {
    await startChat(messageText);
    return;
  }

  if (!isChatActive) {
    await resumeChat();
  }

  const res = await fetch(
    `https://api.livechatinc.com/v3.6/customer/action/send_event?organization_id=${organizationId}`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${customerToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        chat_id: currentChatId,
        event: {
          type: "message",
          text: messageText,
          recipients: "all",
        },
      }),
    },
  );

  const data = await res.json();
  lastEventIds.add(data.event_id);
  appendMessageToChat("You", messageText);
}
```

### Deactivate chat

The `deactivateChat()` function is triggered when the user clicks the **End chat** button.

```js theme={null}
async function deactivateChat() {
  if (!currentChatId) return;

  try {
    const res = await fetch(
      `https://api.livechatinc.com/v3.6/customer/action/deactivate_chat?organization_id=${organizationId}`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${customerToken}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ id: currentChatId }),
      },
    );
    if (res.ok) {
      isChatActive = false;
      appendMessageToChat("System", "The chat has been closed.");
      console.log("🚫 Chat deactivated:", currentChatId);
      stopPolling();
      updateCloseButtonState();
    }
  } catch (e) {
    console.error("Deactivate failed:", e);
  }
}
```

### Poll for messages

Because we're building a client outside of the standard chat widget, we use **polling** — periodically checking the server for new events in the chat every 3 seconds.

```js theme={null}
async function pollForMessages() {
  if (!customerToken || !currentChatId || !currentThreadId) return;

  try {
    const res = await fetch(
      `https://api.livechatinc.com/v3.6/customer/action/list_threads?organization_id=${organizationId}`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${customerToken}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ chat_id: currentChatId }),
      },
    );
    const data = await res.json();
    const thread = data.threads?.find((t) => t.id === currentThreadId);
    if (!thread?.events) return;

    for (const event of thread.events) {
      if (event.type === "message" && !lastEventIds.has(event.id)) {
        const sender = event.author_id === customerId ? "You" : "Agent";
        appendMessageToChat(sender, event.text);
        lastEventIds.add(event.id);
      }
    }
  } catch (e) {
    console.error("Polling error:", e);
  }
}
```

### Putting it all together

At the bottom of `app.js`, event listeners are wired up on page load:

```js theme={null}
window.addEventListener("DOMContentLoaded", async () => {
  await authorizeCustomer();

  document.getElementById("send-btn").addEventListener("click", async () => {
    const input = document.getElementById("message-input");
    const text = input.value.trim();
    if (text) {
      await sendMessage(text);
      input.value = "";
    }
  });

  document.getElementById("close-btn").addEventListener("click", async () => {
    await deactivateChat();
  });
});
```

## Running the app

1. **Start your local server:** Navigate to your project folder and run `npm start`. The terminal will show a local URL, for example `http://localhost:8080`.
2. **Open the app in your browser:** Visit the local URL. You should see the styled chat window.
3. **Log in if needed:** Ensure you are logged into your Text account in the same browser. The app uses cookie-based authorization.
4. **Send a message:** Type a greeting and press **Enter** or click **Send**. Your message should appear in the chat area immediately.
5. **See agent's response:** If an agent replies, their message will appear after up to 3 seconds (the polling interval).
6. **End the chat:** Click the **End chat** button. A system message *"The chat has been closed."* will appear and polling will stop.
7. **Try resuming:** Sending another message after ending the chat will automatically call `resumeChat()` to reopen the conversation.

***

From here, you can expand this basic app in many ways — better error handling, a login prompt if cookie auth fails, real-time (RTM) API instead of polling, or rich messages.
