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

# JavaScript API

> Open and close the widget from your own code, and react to conversation events

## Overview

Once the widget script has loaded, it exposes a small API on `window.CallKaro`.
Use it to open the widget from your own buttons, close it programmatically, and
run your own code when a conversation starts or ends.

Nothing here is required. If you only want the launcher in the corner, the
snippet on its own is enough.

```js theme={null}
CallKaro.open()                       // open the conversation panel
CallKaro.close()                      // close it
CallKaro.on('start', data => { ... }) // listen for events
CallKaro.widgetId                     // the id from your script tag
```

***

## Waiting For The Widget

The snippet uses `defer`, so `window.CallKaro` does **not** exist while your
page is still parsing. Any code that touches it must wait.

The simplest reliable approach is to check inside the handler that needs it,
because by the time a visitor clicks something the widget has long since loaded:

```js theme={null}
document.getElementById('talk-to-us').addEventListener('click', () => {
  if (window.CallKaro) CallKaro.open()
})
```

If you need to run code as soon as the widget is available — to attach event
listeners, for example — poll briefly:

```js theme={null}
function whenCallKaroReady(fn) {
  if (window.CallKaro) return fn(window.CallKaro)
  const timer = setInterval(() => {
    if (window.CallKaro) {
      clearInterval(timer)
      fn(window.CallKaro)
    }
  }, 100)
  setTimeout(() => clearInterval(timer), 10000)  // give up after 10s
}

whenCallKaroReady(ck => {
  ck.on('start', () => console.log('conversation started'))
})
```

<Warning>
  Do not call `CallKaro.open()` on page load to auto-open the widget. It works,
  but opening a voice panel before a visitor has asked for it is intrusive, and
  some browsers will block the microphone prompt because it did not follow a user
  gesture. Trigger it from a click instead.
</Warning>

***

## Methods

### CallKaro.open()

Opens the conversation panel and hides the launcher. Does nothing if the panel
is already open.

Use it to trigger the widget from your own call-to-action:

```html theme={null}
<button id="talk-to-us">Talk to us</button>

<script>
  document.getElementById('talk-to-us').addEventListener('click', () => {
    if (window.CallKaro) CallKaro.open()
  })
</script>
```

<Note>
  Opening the panel does not start a conversation. The visitor still presses
  **Talk** and grants microphone permission themselves.
</Note>

### CallKaro.close()

Closes the panel and restores the launcher. Does nothing if it is already
closed.

```js theme={null}
CallKaro.close()
```

<Warning>
  Closing the panel during an active conversation ends that conversation.
</Warning>

### CallKaro.on(event, callback)

Registers a listener. Returns `CallKaro`, so calls can be chained:

```js theme={null}
CallKaro
  .on('open',  () => console.log('panel opened'))
  .on('start', () => console.log('conversation started'))
  .on('end',   () => console.log('conversation ended'))
```

You can register several listeners for the same event; all of them run. There
is no way to remove a listener once added.

<Info>
  An error thrown inside your callback is caught and logged to the console — it
  will not break the widget.
</Info>

### CallKaro.widgetId

The widget id read from your script tag. Useful when the same analytics code
runs on pages carrying different widgets.

```js theme={null}
console.log(CallKaro.widgetId)   // "wgt_a1b2c3d4"
```

***

## Events

| Event   | Fires when                                             | Payload                         |
| ------- | ------------------------------------------------------ | ------------------------------- |
| `open`  | The panel opens, whether by click or `CallKaro.open()` | `{}`                            |
| `close` | The panel closes                                       | `{}`                            |
| `ready` | The panel has loaded and fetched its configuration     | `{ widgetId }`                  |
| `start` | A conversation begins                                  | `{ state: 'start', sessionId }` |
| `end`   | A conversation ends                                    | `{ state: 'end' }`              |

<Note>
  `ready` fires after the panel is opened for the first time, not on page load —
  the panel is only loaded when somebody opens it.
</Note>

***

## Examples

### Track engagement in your analytics

```js theme={null}
whenCallKaroReady(ck => {
  ck.on('open',  () => gtag('event', 'widget_opened'))
  ck.on('start', d => gtag('event', 'voice_conversation_started', {
    session_id: d.sessionId
  }))
  ck.on('end',   () => gtag('event', 'voice_conversation_ended'))
})
```

### Measure how long conversations last

```js theme={null}
whenCallKaroReady(ck => {
  let startedAt = null
  ck.on('start', () => { startedAt = Date.now() })
  ck.on('end', () => {
    if (!startedAt) return
    const seconds = Math.round((Date.now() - startedAt) / 1000)
    console.log('conversation lasted', seconds, 'seconds')
    startedAt = null
  })
})
```

### Hide your own chat bubble while the widget is open

```js theme={null}
whenCallKaroReady(ck => {
  const bubble = document.querySelector('.my-chat-bubble')
  ck.on('open',  () => { bubble.style.display = 'none' })
  ck.on('close', () => { bubble.style.display = '' })
})
```

### React

```jsx theme={null}
import { useEffect } from 'react'

function TalkToUsButton() {
  useEffect(() => {
    if (!window.CallKaro) return
    window.CallKaro.on('start', () => {
      // your tracking here
    })
  }, [])

  return (
    <button onClick={() => window.CallKaro?.open()}>
      Talk to us
    </button>
  )
}
```

<Warning>
  In React, register listeners once — for example in a top-level component. A
  listener added inside a component that mounts repeatedly will be registered
  again each time, and there is no way to remove it.
</Warning>

***

## Limitations

Worth knowing before you build on this:

* **Listeners cannot be removed.** There is no `off()` method.
* **No way to start a conversation from code.** `open()` shows the panel; the
  visitor presses **Talk**. This is deliberate — browsers require a user
  gesture before granting microphone access.
* **No transcript access.** The conversation text is not exposed to the host
  page. Read transcripts in [Call History](/calls/call-history) instead.
* **One widget per page.** A second snippet on the same page is ignored, and
  `CallKaro` always refers to the first.

<Card title="Need something else?" icon="headset" href="mailto:support@callkaro.ai">
  Tell us what you are trying to build and we will see what we can expose
</Card>
