# JavaScript Web SDK (/docs/sdk-reference/web)




The Encatch Web SDK lets you collect in-app feedback and surveys on your website. Display forms in a built-in modal iframe, identify users, track pages and events, and submit responses to the Encatch backend.

***

## Overview [#overview]

* **Package:** [`@encatch/web-sdk`](https://www.npmjs.com/package/@encatch/web-sdk)
* **Version:** 1.3.0
* **Platforms:** Modern browsers (Chrome, Firefox, Safari, Edge)
* **Repository:** [github.com/get-encatch/web-sdk](https://github.com/get-encatch/web-sdk)

<Callout type="info" title="Prerequisites">
  1. **Publishable SDK key** — Create a key in [Publishable SDK Keys](/docs/settings/security/publishable-sdk-keys). Your site's domain must be listed under **Allowed Domains / Packages**.
  2. **Form ID** — Use the form slug or ID from the Encatch dashboard in `showForm()`.
</Callout>

***

## Installation [#installation]

<Tabs items="['npm', 'yarn', 'pnpm', 'CDN / Script Tag']" defaultIndex="0">
  <Tab value="npm">
    ```bash
    npm install @encatch/web-sdk
    ```
  </Tab>

  <Tab value="yarn">
    ```bash
    yarn add @encatch/web-sdk
    ```
  </Tab>

  <Tab value="pnpm">
    ```bash
    pnpm add @encatch/web-sdk
    ```
  </Tab>

  <Tab value="CDN / Script Tag">
    Pin the CDN version to the same `@encatch/web-sdk` version you use from npm.

    ```html
    <script src="https://cdn.jsdelivr.net/npm/@encatch/web-sdk@1.3.0/dist/encatch.iife.js"></script>
    ```
  </Tab>
</Tabs>

Call `init()` as early as possible in the page lifecycle. Commands issued before the remote implementation script loads are queued automatically.

***

## Quick Start [#quick-start]

### 1. Initialization [#1-initialization]

<Tabs items="['Basic usage', 'Configuration']" defaultIndex="0">
  <Tab value="Basic usage">
    Initialize the SDK with your publishable API key, start a session, and show a form.

    <Tabs items="['ES Modules', 'Script Tag']" defaultIndex="0">
      <Tab value="ES Modules">
        ```javascript
        import { _encatch } from '@encatch/web-sdk';

        _encatch.init('your-api-key');
        _encatch.startSession();
        _encatch.showForm('your-form-slug');
        ```
      </Tab>

      <Tab value="Script Tag">
        ```html
        <script src="https://cdn.jsdelivr.net/npm/@encatch/web-sdk@1.3.0/dist/encatch.iife.js"></script>
        <script>
          _encatch.init('your-api-key');
          _encatch.startSession();
          _encatch.showForm('your-form-slug');
        </script>
        ```
      </Tab>
    </Tabs>

    Only the first `init()` call runs — subsequent calls are ignored. After a successful `identifyUser()`, the SDK starts a session automatically; you do not need `startSession()` before identify.
  </Tab>

  <Tab value="Configuration">
    Pass an optional `config` object to customize SDK behavior:

    <Tabs items="['ES Modules', 'Script Tag']" defaultIndex="0">
      <Tab value="ES Modules">
        ```javascript
        import { _encatch } from '@encatch/web-sdk';

        _encatch.init('your-api-key', {
          theme: 'system',
          apiBaseUrl: 'https://app.encatch.com',
          onBeforeShowForm: async (payload) => {
            // Return false to prevent the built-in iframe from showing
            return true;
          },
        });
        ```
      </Tab>

      <Tab value="Script Tag">
        ```html
        <script>
          _encatch.init('your-api-key', {
            theme: 'system',
            apiBaseUrl: 'https://app.encatch.com',
            onBeforeShowForm: async (payload) => {
              return true;
            },
          });
        </script>
        ```
      </Tab>
    </Tabs>

    <TypeTable
      type="{
  apiBaseUrl: {
    description: 'Base URL for all API calls.',
    type: 'string',
    default: &#x22;'https://app.encatch.com'&#x22;,
    required: false
  },
  webHost: {
    description: 'Base URL for loading the remote SDK implementation script. Defaults to apiBaseUrl.',
    type: 'string',
    required: false
  },
  theme: {
    description: 'Form theme.',
    type: &#x22;'light' | 'dark' | 'system'&#x22;,
    default: &#x22;'system'&#x22;,
    required: false
  },
  onBeforeShowForm: {
    description: 'Interceptor called before any form is shown. Return false to skip the built-in iframe and render a custom UI instead.',
    type: '(payload) => boolean | Promise<boolean>',
    required: false
  }
}"
    />
  </Tab>
</Tabs>

### 2. Identify users [#2-identify-users]

Identify the current user. The `userName` is required (can be a username, email, or unique identifier). Traits and options are optional.

<Tabs items="['Simple', 'With traits', 'Multiple operations', 'Parameters', 'Secure identification']" defaultIndex="0">
  <Tab value="Simple">
    ```javascript
    _encatch.identifyUser('user@example.com');
    ```
  </Tab>

  <Tab value="With traits">
    ```javascript
    _encatch.identifyUser('user@example.com', {
      $set: { name: 'Alice', plan: 'team' },
    });
    ```
  </Tab>

  <Tab value="Multiple operations">
    ```javascript
    _encatch.identifyUser('user@example.com', {
      $set: { name: 'Alice', plan: 'team' },
      $setOnce: { firstSeen: new Date().toISOString() },
      $increment: { loginCount: 1 },
      $decrement: { credits: 5 },
      $unset: ['trialEndDate'],
    });
    ```
  </Tab>

  <Tab value="Parameters">
    <TypeTable
      type="{
  userName: {
    description: 'User identifier (e.g., username, email, user ID).',
    type: 'string',
    required: true
  },
  traits: {
    description: 'User attributes with nested operations.',
    type: 'UserTraits',
    required: false
  },
  options: {
    description: 'Locale, country, and secure identification.',
    type: 'IdentifyOptions',
    required: false
  }
}"
    />

    **User traits** support the following operations:

    | Operation    | Description                                          |
    | ------------ | ---------------------------------------------------- |
    | `$set`       | Set user attributes (overwrites existing values)     |
    | `$setOnce`   | Set user attributes only if they don't already exist |
    | `$increment` | Increment numeric user attributes                    |
    | `$decrement` | Decrement numeric user attributes                    |
    | `$unset`     | Remove user attributes                               |
  </Tab>

  <Tab value="Secure identification">
    <Callout type="warn" title="<strong>Recommended</strong>">
      Using the `secure` option with a server-generated signature is recommended to verify that identification requests come from your backend. **Keep your secret key on the server only** — never expose it in client-side code.
    </Callout>

    Pass a server-generated HMAC signature so Encatch can validate the request. The `generatedDateTimeinUTC` timestamp limits the signature's lifespan.

    ```javascript
    _encatch.identifyUser('user@example.com', undefined, {
      secure: {
        signature: 'your-hmac-signature',
        generatedDateTimeinUTC: '2025-03-13T12:00:00Z',
      },
    });
    ```
  </Tab>
</Tabs>

### 3. Show a form manually [#3-show-a-form-manually]

Show a specific form by slug or ID.

<Tabs items="['Example', 'Parameters', 'Options']" defaultIndex="0">
  <Tab value="Example">
    ```javascript
    _encatch.showForm('feedback-form');
    _encatch.showForm('feedback-form', { reset: 'always' });
    ```
  </Tab>

  <Tab value="Parameters">
    <TypeTable
      type="{
  formId: {
    description: 'The slug or ID of the form to display.',
    type: 'string',
    required: true
  },
  options: {
    description: 'Controls when form data is cleared.',
    type: 'ShowFormOptions',
    required: false
  }
}"
    />
  </Tab>

  <Tab value="Options">
    <TypeTable
      type="{
  reset: {
    description: 'When to clear form data.',
    type: &#x22;'always' | 'on-complete' | 'never'&#x22;,
    default: &#x22;'always'&#x22;,
    required: false
  },
  context: {
    description: 'Arbitrary key-value metadata attached to the form submission. Date values are serialized to ISO 8601 strings.',
    type: 'Record<string, ContextValue>',
    required: false
  }
}"
    />

    | Reset mode      | Behavior                                               |
    | --------------- | ------------------------------------------------------ |
    | `'always'`      | Reset pre-fill and response data on every form display |
    | `'on-complete'` | Reset only after the form is completed                 |
    | `'never'`       | Never reset response data                              |

    Pass caller context when showing a form:

    ```javascript
    _encatch.showForm('feedback-form', {
      reset: 'always',
      context: { plan: 'team', feature: 'checkout' },
    });
    ```

    The `context` object is exposed as `context.*` in [Logic jumps](/docs/feedback-management/form-builder/logic-jumps) Start conditions.
  </Tab>
</Tabs>

### Other actions [#other-actions]

<Accordions type="single">
  <Accordion title="Locale">
    Set the user's preferred language.

    ```javascript
    _encatch.setLocale('fr,en,es');
    ```
  </Accordion>

  <Accordion title="Country">
    Set the user's country.

    ```javascript
    _encatch.setCountry('FR'); // ISO 3166 country code
    ```
  </Accordion>

  <Accordion title="Theme">
    Set the theme for forms and surveys.

    ```javascript
    _encatch.setTheme('dark');
    _encatch.setTheme('light');
    _encatch.setTheme('system'); // Follows system preference
    ```
  </Accordion>

  <Accordion title="Track events">
    ```javascript
    _encatch.trackEvent('button_clicked');
    ```

    Requires a device id from `startSession()` or a successful `identifyUser()`.
  </Accordion>

  <Accordion title="Track screens">
    ```javascript
    _encatch.trackScreen('Dashboard');
    ```

    With an active session, SPA navigations (`pushState`, `replaceState`, `popstate`) also call `trackScreen` with the full page URL automatically.
  </Accordion>

  <Accordion title="Source tracking">
    Merge campaign parameters into the source tracking store. Values set here take precedence over URL query params on key collision. Web SDK only.

    ```javascript
    _encatch.addSourceTracking({
      utm_campaign: 'spring-sale',
    });
    _encatch.showForm('feedback-form');
    ```

    On each `showForm()`, the SDK merges the page URL query string with `addSourceTracking()` values. The server filters keys per form; filtered values are auto-injected into `submitForm` for custom UIs.
  </Accordion>

  <Accordion title="Listen to form events">
    Subscribe to form lifecycle events. Returns an unsubscribe function.

    <Tabs items="['Example', 'Event Types']" defaultIndex="0">
      <Tab value="Example">
        ```javascript
        const unsubscribe = _encatch.on((eventType, payload) => {
          console.log('Event:', eventType, payload.data);
        });
        // Later:
        unsubscribe();
        ```
      </Tab>

      <Tab value="Event Types">
        | Event                 | Description                                                                     |
        | --------------------- | ------------------------------------------------------------------------------- |
        | `form:show`           | Fired when a form is displayed                                                  |
        | `form:started`        | Fired when a user starts interacting                                            |
        | `form:submit`         | Fired when a form is submitted                                                  |
        | `form:complete`       | Fired when a form is fully completed                                            |
        | `form:close`          | Fired when a form is closed                                                     |
        | `form:dismissed`      | Fired when a form is dismissed without completion                               |
        | `form:error`          | Fired when an error occurs                                                      |
        | `form:section:change` | Fired when the visible section changes                                          |
        | `form:answered`       | Fired when a question is answered                                               |
        | `form:remindmelater`  | Fired when the user taps "Remind me later" (hides iframe only — no dismiss API) |
        | `form:ctaTriggered`   | Fired when a completion CTA is triggered on thank-you or exit screens           |
      </Tab>
    </Tabs>

    Handle completion CTAs via `form:ctaTriggered`:

    ```javascript
    _encatch.on((eventType, payload) => {
      if (eventType !== 'form:ctaTriggered') return;

      const action = payload.data?.action;
      if (action === 'app_navigate') {
        const route = payload.data?.route;
        // Navigate in your SPA — the SDK closes the iframe automatically
        window.location.hash = route;
      }
    });
    ```

    For `app_navigate`, the SDK closes the form iframe after emitting the event and expects the host to perform in-app navigation. For `redirect_internal` and `redirect_external`, the SDK opens the URL and closes the form automatically.
  </Accordion>

  <Accordion title="Pre-fill responses">
    Pre-fill a form response before showing a form. `questionId` may be a question UUID or a question slug.

    ```javascript
    _encatch.addToResponse('question_id', 'pre-filled value');
    _encatch.addToResponse('choice_question_id', ['option-a', 'option-b']);

    _encatch.showForm('your-form-slug');
    ```

    Updates apply on the next `showForm()` or immediately to a visible iframe.
  </Accordion>

  <Accordion title="Dismiss form">
    Dismiss the currently displayed form.

    ```javascript
    _encatch.dismissForm();
    // Or dismiss a specific form configuration:
    _encatch.dismissForm('form-config-id');
    ```
  </Accordion>

  <Accordion title="Form interceptor">
    Use `onBeforeShowForm` in `init()` config to conditionally block forms from showing or render a custom UI instead.

    ```javascript
    _encatch.init('your-api-key', {
      onBeforeShowForm: async (payload) => {
        // Inspect payload.formId, payload.formConfig, payload.triggerType, etc.
        if (payload.triggerType === 'automatic' && someCondition) {
          return false; // Block this form or use custom UI
        }
        return true; // Allow built-in iframe
      },
    });
    ```

    **`onBeforeShowForm` payload:**

    <TypeTable
      type="{
  formId: { description: 'Form slug or ID', type: 'string', required: true },
  formConfig: { description: 'Show-form API response', type: 'object', required: true },
  resetMode: { description: &#x22;'always' | 'on-complete' | 'never'&#x22;, type: 'string', required: true },
  triggerType: { description: &#x22;'manual' | 'automatic'&#x22;, type: 'string', required: true },
  prefillResponses: { description: 'Values from addToResponse()', type: 'object', required: true },
  locale: { description: 'Active locale', type: 'string', required: false },
  theme: { description: 'Active theme', type: 'string', required: false },
  context: { description: 'Serialized showForm context', type: 'object', required: false }
}"
    />
  </Accordion>

  <Accordion title="Session">
    Start and control the session lifecycle manually:

    <Tabs items="['Start', 'Pause / Resume', 'Stop', 'Reset user', 'Clear all']" defaultIndex="0">
      <Tab value="Start">
        ```javascript
        _encatch.startSession();

        // Skip the immediate ping or screen re-track on start:
        _encatch.startSession({
          skipImmediatePing: true,
          skipImmediateTrackScreen: true,
        });
        ```

        Each `startSession()` generates a new session id (24-hour rolling expiry).
      </Tab>

      <Tab value="Pause / Resume">
        ```javascript
        // Temporarily stop the background ping (not persisted)
        _encatch.pauseSession();

        // Resume the ping interval after pauseSession()
        _encatch.resumeSession();
        ```
      </Tab>

      <Tab value="Stop">
        ```javascript
        // Fully suspend SDK activity — stops ping, URL listeners, and dismisses open forms.
        // Persists across page reloads. Re-enable with startSession().
        _encatch.stopSession();
        ```
      </Tab>

      <Tab value="Reset user">
        Reset the current user identity and clear persisted identity data. Reverts the SDK to anonymous mode. User identity is preserved across `stopSession()` — use `resetUser()` after logout.

        ```javascript
        _encatch.resetUser();
        ```
      </Tab>

      <Tab value="Clear all">
        Wipes **all** persisted SDK data and resets in-memory state. Stronger than `resetUser()` — also clears session-stopped state and device preferences. Tracking stops until `startSession()` or `identifyUser()` is called again.

        ```javascript
        _encatch.clearAll();
        ```
      </Tab>
    </Tabs>

    The SDK sends a background ping every 30 seconds (configurable via server response) to maintain engagement sessions and check for triggered forms. Ping is suppressed while a form is visible.

    <Callout type="info" title="Reload behavior">
      If a prior session or user exists in browser storage, the SDK restores identifiers on load and may restart the session unless it was explicitly stopped.
    </Callout>
  </Accordion>
</Accordions>

***

### Full-screen / Inline mode [#full-screen--inline-mode]

Not available at the moment.

### Build Your Own Form UX & UI [#build-your-own-form-ux--ui]

If your feedback flow uses a **fixed, predictable question set** — the same fields and workflow every time — you can build the form with your own HTML/CSS/JS and submit responses through the SDK. That keeps typography, spacing, colors, and interaction patterns aligned with the rest of your site, so the survey feels like part of your app rather than an embedded iframe.

Example coming soon.

***

### Content Security Policy [#content-security-policy]

If your site uses CSP headers, whitelist Encatch for SDK loading, HTTP API calls, and the form iframe. Add the CDN host only when you install through the script tag.

```http
Content-Security-Policy:
  script-src 'self' https://app.encatch.com https://cdn.jsdelivr.net;
  connect-src 'self' https://app.encatch.com; /* fetch/XHR API calls */
  frame-src 'self' https://app.encatch.com;
```

The form's CSS loads inside the Encatch iframe. If your CSP blocks inline styles and the modal wrapper is not positioned correctly, allow inline styles in `style-src` for the host page.

If you configure a custom `webHost`, whitelist that host instead of `https://app.encatch.com` for script loading.

***

<Accordions type="single">
  <Accordion title="API quick reference">
    | Method                                      | Description                             |
    | ------------------------------------------- | --------------------------------------- |
    | `init(apiKey, config?)`                     | Initialize the SDK                      |
    | `identifyUser(userName, traits?, options?)` | Identify a user                         |
    | `setLocale(locale)`                         | Set locale                              |
    | `setCountry(country)`                       | Set country (ISO 3166)                  |
    | `setTheme(theme)`                           | Set form theme                          |
    | `trackEvent(eventName)`                     | Track a custom event                    |
    | `trackScreen(screenName)`                   | Track screen navigation                 |
    | `showForm(formId, options?)`                | Show a form in the modal iframe         |
    | `dismissForm(formConfigurationId?)`         | Dismiss the current form                |
    | `addToResponse(questionId, value)`          | Pre-fill a question answer              |
    | `addSourceTracking(values)`                 | Merge source tracking params (web only) |
    | `startSession(options?)`                    | Start a new session                     |
    | `pauseSession()`                            | Pause background ping                   |
    | `resumeSession()`                           | Resume background ping                  |
    | `stopSession()`                             | Suspend SDK activity                    |
    | `resetUser()`                               | Reset user identity                     |
    | `clearAll()`                                | Wipe all persisted SDK data             |
    | `on(callback)`                              | Subscribe to lifecycle events           |
    | `submitForm(params)`                        | Submit a custom form                    |
    | `emitEvent(eventType, payload)`             | Emit a lifecycle event (custom UI)      |
    | `refineText(params)`                        | AI text refinement                      |
    | `uploadFile(params)`                        | Upload a file (custom forms)            |
    | `qnaWithAi(params)`                         | Q\&A with AI                            |
    | `streamQnaWithAi(params, callbacks)`        | Streaming Q\&A with AI                  |
  </Accordion>
</Accordions>

## Support [#support]

* **npm:** [@encatch/web-sdk](https://www.npmjs.com/package/@encatch/web-sdk)
* **Issues:** [github.com/get-encatch/web-sdk/issues](https://github.com/get-encatch/web-sdk/issues)
