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

# JavaScript web widget

> Install the woku capture widget on any website with a lightweight <script> loader and an iframe hosted on the woku CDN

The **woku web widget** is a micro-app hosted on the woku CDN
(`cdn.woku.app`) that captures **woku** ratings (1 to 5) or **NPS**
scores (0 to 10) directly from your website. You embed it with a
**lightweight `<script>` loader** (\~2.5 KB gzip) that injects an
`<iframe>` when a trigger fires; you do not need to load any CSS or
frameworks on your page.

<Note>
  All of the widget's styles live inside the `<iframe>`, in an isolated
  document. There is no risk of them clashing with your site's styles, nor
  of your JavaScript interfering with the widget's.
</Note>

## How it works

1. Your page loads the **loader** from `cdn.woku.app`.
2. You call `WokuWidget.init(config)` with your configuration.
3. The loader evaluates the **triggers** (time, scroll, exit, event,
   click) and, when the first one fires, **injects an `<iframe>`** that
   points to the hosted micro-app.
4. The micro-app renders the mode based on `captureType` (`woku` shows
   1-5 stars; `nps` shows a 0-10 scale), captures the feedback, and
   sends it to the woku API.
5. The loader emits events (`open`, `submit`, `close`, `skip`) that you
   can listen to with `WokuWidget.on(...)`.

## Installation

<Steps>
  <Step title="Get your publishable key">
    In the admin application, go to **Company → Integrations** and copy
    the **publishable key** (`pk_...`). It is safe to embed in your
    site's public HTML.
  </Step>

  <Step title="Paste the loader into your site">
    Place this snippet before `</body>` on the pages where you want to
    show the widget.

    ```html theme={null}
    <!-- Woku Widget loader -->
    <script src="https://cdn.woku.app/sdks/woku-widget/v1/loader.js"></script>
    <script>
      WokuWidget.init({
        companyId: 'TU_COMPANY_ID',
        publishableKey: 'pk_live_...',
        captureType: 'nps',          // 'woku' | 'nps'
        // npsToolId: 'NPS_TOOL_ID',  // optional for NPS
        triggers: [
          { type: 'time', value: 5, behavior: 'modal' },
        ],
      });
    </script>
    ```
  </Step>

  <Step title="Verify">
    Open your site and wait for the trigger to fire (in the example, 5
    seconds). The widget will appear as a modal.
  </Step>
</Steps>

<Warning>
  **Always** use the publishable key (`pk_...`) in the widget, **never** the
  secret API key. The widget is public code: anyone can inspect its
  configuration. The publishable key only allows capturing feedback; it
  does not expose any data from your account.
</Warning>

## Authentication: publishable key

The widget authenticates with your company's **publishable key**, sending
it in the `x-woku-key` header of every capture. Unlike the secret key
(used by server-side integrations), the publishable key:

* Starts with the `pk_` prefix (e.g. `pk_live_...`).
* Is **safe to embed** in public HTML.
* Only has **capture** permission (it does not read or manage resources).

You can find it in **Company → Integrations** of the admin panel
([https://admin.woku.app](https://admin.woku.app)).

## Configuration

The object you pass to `WokuWidget.init(...)` accepts these fields:

| Field            | Required    | Description                                                                   |
| ---------------- | ----------- | ----------------------------------------------------------------------------- |
| `companyId`      | yes         | Identifier of your company.                                                   |
| `publishableKey` | yes         | Public key `pk_...` (`x-woku-key` header).                                    |
| `captureType`    | yes         | `'woku'` (1-5 stars) or `'nps'` (0-10 scale).                                 |
| `wokuId`         | conditional | Required when `captureType === 'woku'`.                                       |
| `npsToolId`      | no          | For NPS; if omitted, the NPS is at the company level.                         |
| `apiBaseUrl`     | no          | Base URL of the API. Defaults to `https://clientapi.woku.app`.                |
| `widgetBaseUrl`  | no          | Origin of the iframe. Defaults to `https://cdn.woku.app/sdks/woku-widget/v1`. |
| `lang`           | no          | `'es'` or `'en'`. If omitted, it is auto-detected from the browser.           |
| `branding`       | no          | Shows "Powered by Woku". Defaults to `true`.                                  |
| `theme`          | no          | Visual customization (see [Theme](#theme)).                                   |
| `triggers`       | yes         | List of conditions that show the widget (see [Triggers](#triggers)).          |
| `urlRules`       | no          | Include/exclude rules by URL (regex).                                         |

### Capture type: woku vs NPS

<Tabs>
  <Tab title="woku (1-5 stars)">
    Captures a rating of 1 to 5 stars associated with a `wokuId`.

    ```html theme={null}
    <script src="https://cdn.woku.app/sdks/woku-widget/v1/loader.js"></script>
    <script>
      WokuWidget.init({
        companyId: 'TU_COMPANY_ID',
        publishableKey: 'pk_live_...',
        captureType: 'woku',
        wokuId: 'WOKU_ID',           // required in woku mode
        triggers: [
          { type: 'exit-intent', behavior: 'modal' },
        ],
      });
    </script>
    ```

    In `woku` mode the user sees 1-5 stars and, optionally, leaves a
    comment as text or audio.
  </Tab>

  <Tab title="NPS (0-10)">
    Captures an NPS score from 0 to 10. The `npsToolId` is optional: if
    you omit it, the capture is associated with the company-level NPS.

    ```html theme={null}
    <script src="https://cdn.woku.app/sdks/woku-widget/v1/loader.js"></script>
    <script>
      WokuWidget.init({
        companyId: 'TU_COMPANY_ID',
        publishableKey: 'pk_live_...',
        captureType: 'nps',
        npsToolId: 'NPS_TOOL_ID',    // optional
        triggers: [
          { type: 'time', value: 10, behavior: 'side-tab' },
        ],
      });
    </script>
    ```

    woku automatically categorizes each response: detractor (0-6),
    passive (7-8), or promoter (9-10).
  </Tab>
</Tabs>

### Language (i18n)

The widget includes two languages: **Spanish** (`es`, default) and
**English** (`en`). The language is resolved in this order:

1. The `lang` field of the configuration.
2. The browser language (`navigator.language`).
3. Fallback to `es`.

<Tabs>
  <Tab title="Spanish">
    ```js theme={null}
    WokuWidget.init({
      companyId: 'TU_COMPANY_ID',
      publishableKey: 'pk_live_...',
      captureType: 'nps',
      lang: 'es',
      triggers: [{ type: 'time', value: 5, behavior: 'modal' }],
    });
    ```
  </Tab>

  <Tab title="English">
    ```js theme={null}
    WokuWidget.init({
      companyId: 'TU_COMPANY_ID',
      publishableKey: 'pk_live_...',
      captureType: 'nps',
      lang: 'en',
      triggers: [{ type: 'time', value: 5, behavior: 'modal' }],
    });
    ```
  </Tab>

  <Tab title="Auto-detection">
    ```js theme={null}
    // Without lang: navigator.language is used, and Spanish as a fallback.
    WokuWidget.init({
      companyId: 'TU_COMPANY_ID',
      publishableKey: 'pk_live_...',
      captureType: 'nps',
      triggers: [{ type: 'time', value: 5, behavior: 'modal' }],
    });
    ```
  </Tab>
</Tabs>

### Triggers

Each trigger defines **when** the widget appears and **how** it is
presented (`behavior`). You can combine several; the first one that is
met fires.

```ts theme={null}
interface TriggerConfig {
  type: 'time' | 'scroll' | 'exit-intent' | 'custom-event' | 'click-selector';
  value?: number | string; // seconds, scroll %, event name, or CSS selector
  behavior: 'modal' | 'banner' | 'side-tab' | 'fullscreen';
}
```

| `type`           | `value`      | When it fires                                             |
| ---------------- | ------------ | --------------------------------------------------------- |
| `time`           | seconds      | After N seconds on the page.                              |
| `scroll`         | percentage   | When reaching N% of scroll.                               |
| `exit-intent`    |              | When moving the cursor outside the window (desktop only). |
| `custom-event`   | event name   | When `window.dispatchEvent(new Event(name))` fires.       |
| `click-selector` | CSS selector | When clicking an element that matches.                    |

Presentation modes (`behavior`): `modal` (centered window with overlay),
`banner` (fixed bar), `side-tab` (side tab), and `fullscreen` (full
screen).

```js theme={null}
WokuWidget.init({
  companyId: 'TU_COMPANY_ID',
  publishableKey: 'pk_live_...',
  captureType: 'nps',
  triggers: [
    { type: 'scroll', value: 70, behavior: 'banner' },
    { type: 'click-selector', value: '#dar-feedback', behavior: 'modal' },
  ],
});
```

### Theme

Customize the widget's appearance. The values are applied as CSS custom
properties inside the iframe (except `zIndex`, which controls the overlay
on your page):

```js theme={null}
WokuWidget.init({
  // ...
  theme: {
    primaryColor: '#1447e6',
    fontFamily: 'Inter, sans-serif',
    borderRadius: '12px',
    zIndex: 9999,
  },
});
```

## Programmatic API

The loader exposes `window.WokuWidget`:

```js theme={null}
WokuWidget.show();    // manually shows the widget
WokuWidget.hide();    // hides it
WokuWidget.destroy(); // removes the iframe and clears the listeners

WokuWidget.on('open',   () => { /* the widget opened */ });
WokuWidget.on('close',  () => { /* the widget closed */ });
WokuWidget.on('submit', (data) => { console.log('submitted:', data); });
WokuWidget.on('skip',   () => { /* the user skipped the feedback */ });
```

## CSP requirements

If your site uses **Content-Security-Policy**, you must allow the CDN
origin both for the loader script and for the micro-app iframe:

```
Content-Security-Policy:
  script-src cdn.woku.app;   /* el loader <script> */
  frame-src  cdn.woku.app;   /* el iframe de la micro-app */
```

<Note>
  The loader is loaded as an external `<script src="...">` and does **not**
  use `eval` or inline scripts, so you do not need to configure a `nonce`
  or `unsafe-inline`.
</Note>

## Troubleshooting

<AccordionGroup>
  <Accordion title="The widget does not appear">
    * Confirm that some **trigger** is met (for example, wait out the
      seconds of a `time` trigger).
    * Check the browser console: CSP errors indicate that you are missing
      permission to allow `cdn.woku.app` in `script-src` or `frame-src`.
    * Verify that `companyId` and `publishableKey` are correct.
  </Accordion>

  <Accordion title="401 error when sending feedback">
    The `publishableKey` is invalid or does not correspond to the
    `companyId`. Copy it again from **Company → Integrations**.
  </Accordion>

  <Accordion title="Nothing loads in woku mode">
    `wokuId` is missing, which is **required** when
    `captureType === 'woku'`.
  </Accordion>

  <Accordion title="The iframe looks cut off on mobile">
    The widget adjusts its height automatically via `postMessage`. If
    your site applies `overflow: hidden` or a `transform` to the
    `<body>`, it may interfere with the overlay. Use
    `behavior: 'fullscreen'` as an alternative.
  </Accordion>
</AccordionGroup>
