> For the complete documentation index, see [llms.txt](https://docs.klink.cloud/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.klink.cloud/getting-started/setup-channels/live-chat/chat-widget-custom-buttons-and-programmatic-control.md).

# Chat Widget - Custom Buttons & Programmatic Control

Embed the klink.cloud chat widget on your site, hide the default launcher, and open chat from your own buttons or events.

#### Installation

Add the script inside the `<head>` tag of your HTML. It uses `async` and `defer`, so it won't block page rendering.

```html
<head>
  <script>
    (function (d, t) {
      var SCRIPT_PATH = "https://chat.klink.cloud/klink-chat-widget.embed.latest.js";
      var g = d.createElement(t),
          s = d.getElementsByTagName(t)[0];
      g.src = SCRIPT_PATH;
      g.defer = true;
      g.async = true;
      s.parentNode.insertBefore(g, s);
      g.onload = function () {
        window.klinkChatSDK.run({
          serverUrl: "https://apigw.klink.cloud",
          scId: "YOUR_SC_ID_HERE",
        });
      };
    })(document, 'script');
  </script>
</head>
```

{% hint style="warning" %}
**Update your script URL.** Older versions of this page used `https://webchat.klink.cloud/widget/klink-chat-widget.umd.js`. That address no longer works, so replace it with the URL above.

Don't add a cache-busting parameter such as `?v=<timestamp>` either. It makes every visitor download the full widget on every page view. The widget is already served with the correct caching, and updates reach your site within minutes.
{% endhint %}

To receive only non-breaking updates, or to pin an exact version (recommended for mobile apps), see [Chat Widget - SDK Versions & URLs](/getting-started/setup-channels/live-chat/chat-widget-sdk-versions-and-urls.md).

**Framework notes:** In Next.js, use `<Script strategy="afterInteractive">` in your root layout. In Vite or CRA, paste the snippet into `public/index.html`. In WordPress, use a headers plugin or edit `header.php`.

***

#### Configuration Options

| Option             | Type     | Required | Description                                                                                     |
| ------------------ | -------- | -------- | ----------------------------------------------------------------------------------------------- |
| `serverUrl`        | string   | Yes      | Always `https://apigw.klink.cloud`                                                              |
| `scId`             | string   | Yes      | Your Social Channel ID from klink.cloud settings                                                |
| `showLaunchButton` | boolean  | No       | Set to `false` to hide the default floating button                                              |
| `showHeader`       | boolean  | No       | Set to `false` to hide the chat header (title, close button). Defaults to `true`                |
| `authUser`         | object   | No       | Identifies a logged-in visitor. See [Passing Authenticated Users](#passing-authenticated-users) |
| `onReady`          | function | No       | Called once the conversation has loaded and the visitor can type                                |
| `onError`          | function | No       | Called if the widget can't start                                                                |
| `onClose`          | function | No       | Called every time the visitor closes the chat                                                   |

***

#### Hide the Default Button & Open Programmatically

To integrate chat into your own UI — a nav link, hero CTA, or custom icon — hide the default launcher and call `openWidget()` when you need it.

```javascript
window.klinkChatSDK.run({
  serverUrl: "https://apigw.klink.cloud",
  scId: "YOUR_SC_ID_HERE",
  showLaunchButton: false,
});
```

Then trigger it from any click:

```html
<button onclick="window.klinkChatSDK.openWidget()">
  Chat with Support
</button>
```

**React / Next.js**

```jsx
<button onClick={() => window.klinkChatSDK.openWidget()}>
  Talk to us
</button>
```

**Link**

```html
<a href="#" onclick="window.klinkChatSDK.openWidget(); return false;">
  Need help? Chat now
</a>
```

To close the chat from your own code, call:

```javascript
window.klinkChatSDK.closeWidget();
```

***

#### Use Your Own Header and Close Button

If the chat sits inside your own panel or modal, you can hide the widget's header and use your own close button:

```javascript
window.klinkChatSDK.run({
  serverUrl: "https://apigw.klink.cloud",
  scId: "YOUR_SC_ID_HERE",
  showLaunchButton: false,
  showHeader: false,
});
```

If you keep the widget's header, use `onClose` to react when the visitor closes the chat, for example to hide your own container:

```javascript
window.klinkChatSDK.run({
  serverUrl: "https://apigw.klink.cloud",
  scId: "YOUR_SC_ID_HERE",
  onClose: () => document.getElementById("chat-panel").hidden = true,
});
```

***

#### Loading and Error Events

Use `onReady` to hide your own loading spinner as soon as the chat is usable, and `onError` to show a fallback if it can't start:

```javascript
window.klinkChatSDK.run({
  serverUrl: "https://apigw.klink.cloud",
  scId: "YOUR_SC_ID_HERE",
  onReady: ({ identityVerified }) => hideSpinner(),
  onError: ({ reason }) => showContactFormInstead(),
});
```

| Event     | When it fires                                                     | Details                                                                                                                                                                           |
| --------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onReady` | Once, when the conversation has loaded                            | `identityVerified`: `true` if the logged-in user was verified (see [Identity Verification](/getting-started/setup-channels/live-chat/identity-verification-secure-user-login.md)) |
| `onError` | Once, if the widget can't start                                   | `reason`: `config_fetch_failed`, `connection_failed`, or `origin_not_allowed` (this site isn't in your whitelisted domains)                                                       |
| `onClose` | Every time the visitor closes the chat (close button or launcher) | —                                                                                                                                                                                 |

The same events are also available as window events: `klinkChatWidgetReady`, `klinkChatWidgetError` and `klinkChatWidgetClosed`.

***

#### Passing Authenticated Users

For logged-in visitors, pass their identity so conversations attribute to a known contact instead of an anonymous session.

```javascript
window.klinkChatSDK.run({
  serverUrl: "https://apigw.klink.cloud",
  scId: "YOUR_SC_ID_HERE",
  showLaunchButton: false,
  authUser: {
    userToken: tokenFromYourServer, // signed on your server
  },
});
```

{% hint style="info" %}
To stop anyone from pretending to be another user, identities should be **verified**. Your server creates a short-lived signed token (`userToken`). See [Identity Verification (Secure User Login)](/getting-started/setup-channels/live-chat/identity-verification-secure-user-login.md) for the full setup.
{% endhint %}

In React, wait for the session to hydrate before initializing:

```jsx
useEffect(() => {
  if (session?.user && window.klinkChatSDK) {
    window.klinkChatSDK.run({
      serverUrl: "https://apigw.klink.cloud",
      scId: "YOUR_SC_ID_HERE",
      showLaunchButton: false,
      authUser: { userToken: session.chatToken },
    });
  }
}, [session]);
```

***

#### Login, Logout and Switching Users

| Situation                        | What to do                                                                                                                                                 |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A user logs in                   | Call `run()` with their `authUser`. If the chat is already running, it's replaced, so you never get two widgets                                            |
| A user logs out                  | Call `await window.klinkChatSDK.reset()`. This removes the chat and forgets the visitor, so the next person on the same device starts a fresh conversation |
| Continue as a guest after logout | Call `run()` again without `authUser` after `reset()`                                                                                                      |

```javascript
async function onLogout() {
  await window.klinkChatSDK.reset();
  window.klinkChatSDK.run({ serverUrl: "https://apigw.klink.cloud", scId: "YOUR_SC_ID_HERE" });
}
```

***

#### Notes

* Calling `run()` again replaces the current chat; it never creates a second widget.
* Only pass `authUser` when you have real values; omit the field otherwise.
* If `window.klinkChatSDK` is undefined, the script hasn't finished loading yet.
* Check which widget version loaded with `window.klinkChatSDK.version` (see [SDK Versions & URLs](/getting-started/setup-channels/live-chat/chat-widget-sdk-versions-and-urls.md)).
* If you set **whitelisted domains** for your channel, the widget only loads on those sites (see [Live Chat](/getting-started/setup-channels/live-chat.md)).
