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

# zudo.js

> Add a small script to your product so Zudo knows who is signed in, which account they belong to, and what they are doing — and can show them an in-app NPS survey.

zudo.js is a script you add to your own product. It tells Zudo who is signed in and which account they belong to, sends product-usage events, and shows the [in-app NPS survey](/accounts/nps).

It is about 5 KB gzipped, has no dependencies, and loads deferred so it never blocks your page.

<Note>
  zudo.js is **org-level**. The connection and its key belong to your organization, and an owner or admin sets it up. If
  you don't see the setup button, ask your org owner.
</Note>

## Before you start

You'll need:

* An **owner** or **admin** role in your Zudo organization
* Somewhere in your product's HTML you can add a script tag
* Your own id for the signed-in user, and your own id for the account they belong to

## Set it up

<Steps>
  <Step title="Create the connection">
    Go to **Settings › NPS survey** and choose **Set up zudo.js**. Zudo generates a publishable key and shows you the
    snippet with the key already in it.
  </Step>

  <Step title="Paste the snippet">
    Add it before the closing `</body>` tag, and replace the placeholders with your own values.

    ```html theme={null}
    <script>
    	(function (w, n) {
    		var q = (w[n] = w[n] || []);
    		for (var m = ["init", "organization", "account", "user", "track", "nps", "reset"], i = 0; i < m.length; i++)
    			(function (k) {
    				q[k] =
    					q[k] ||
    					function () {
    						q.push([k, [].slice.call(arguments)]);
    					};
    			})(m[i]);
    	})(window, "Zudo");
    	Zudo.init("zjs_pub_your_key_here");
    	Zudo.account({
    		accountId: CURRENT_ACCOUNT_ID,
    		traits: {
    			name: CURRENT_ACCOUNT_NAME,
    			createdAt: ACCOUNT_CREATED_AT,
    		},
    	});
    	Zudo.user({
    		userId: CURRENT_USER_ID,
    		accountId: CURRENT_ACCOUNT_ID,
    		traits: {
    			name: CURRENT_USER_NAME,
    			email: CURRENT_USER_EMAIL,
    		},
    	});
    	Zudo.nps("survey");
    </script>
    <script src="https://zudo.so/zudo.js/v1/zudo.js" defer></script>
    ```

    The first block is a queue. It records any call your product makes before the script has finished loading and replays
    it once zudo.js arrives, so you can identify your user in your own bootstrap without waiting on us.
  </Step>

  <Step title="Check it arrived">
    Reload your product, then look at **Settings › NPS survey**. The install section shows when Zudo last heard from your
    key. If it still says it has heard nothing, see [Troubleshooting](#troubleshooting).
  </Step>

  <Step title="Restrict the origins">
    While you are setting up, the key works from anywhere. Once you know which domains your product runs on, list them
    under **Allowed origins** and only those may send.
  </Step>
</Steps>

## The publishable key

The key is not a secret. It is printed in your HTML, where anyone visiting your product can read it, and that is by
design — the same is true of every analytics snippet.

What protects you is that the key can only write. No endpoint it authenticates returns anything about your accounts,
your contacts or your data. On top of that:

* **Allowed origins.** Once you set a list, requests from anywhere else are refused.
* **Rate limits.** Ingest is limited per organization, the same as the Segment and PostHog integrations.
* **Revocable on its own.** Rotate or delete a key without touching anything else you have connected.

## Methods

### `Zudo.init(key, options?)`

Points the library at your organization. Call it before anything else.

```js theme={null}
Zudo.init("zjs_pub_...");
Zudo.init("zjs_pub_...", { autoLoadSegment: true });
```

| Option | Default | Does |
| - | - | - |
| `host` | `https://zudo.so` | Where to send. Change it only if you proxy Zudo. |
| `autoLoadSegment` | `false` | Take identity from Segment's `analytics.identify` and `analytics.group` instead of calling `user` and `account` yourself. |

### `Zudo.account(payload)`

The account the signed-in person belongs to.

```js theme={null}
Zudo.account({
	accountId: "acct_1842",
	traits: { name: "Acme", createdAt: "2024-03-01", plan: "Growth", seats: 48 },
});
```

`accountId` should be the id you already use for that customer. If it matches an account's **External ID** in Zudo,
events and responses land on that account. If no account matches, Zudo can create one — see
[Auto-creating accounts and contacts](/integrations/event-auto-create).

Traits you send become [Zudo traits](/accounts/overview) on the account, usable in segments, playbooks and health
scoring like any other.

<Warning>
  Send `createdAt` here, on the **account** call, not on the `user` call. The survey's minimum account age reads it
  as an account trait. Without it, Zudo measures from the day it first saw the account — which for a fresh install is
  today, so nobody is eligible for as long as your minimum age is set to.
</Warning>

### `Zudo.user(payload)`

The signed-in person.

```js theme={null}
Zudo.user({
	userId: "user_9931",
	accountId: "acct_1842",
	traits: { name: "Dana Reyes", email: "dana@acme.test", role: "admin" },
});
```

Call this on every page load. It is also what counts a **session** — a day on which this person used your product,
counted once per day however many times they visit — which the survey's minimum-sessions rule reads.

### `Zudo.organization(payload)`

If your product has a layer above accounts, name it here.

```js theme={null}
Zudo.organization({ organizationId: "org_55", traits: { name: "Acme Group" } });
```

### `Zudo.track(payload)`

Something the person did.

```js theme={null}
Zudo.track({ event: "report_exported", properties: { format: "csv", rows: 1200 } });
```

Events are batched and sent together, and flushed when the page closes. They arrive in the account timeline and the
daily counts chart, and can drive [indicators](/accounts/indicators) — the same pipeline the
[Segment](/integrations/segment) and [PostHog](/integrations/posthog) integrations use. Only events on your allowlist
are rolled up; see [Product events](/accounts/product-events).

### `Zudo.nps(mode?, options?)`

Shows the NPS survey, if this person is due one. See [NPS surveys](/accounts/nps) for the rules and the wording.

```js theme={null}
Zudo.nps("survey"); // respects your settings — this is the one for production
Zudo.nps("show"); // ignores the settings, records the answer
Zudo.nps("test"); // ignores the settings, records nothing
Zudo.nps("survey", { productName: "Acme", delay: 0 });
```

| Option | Does |
| - | - |
| `productName` | Fills `{{productName}}` in the question |
| `delay` | Milliseconds before the panel appears. Overrides the configured delay. |
| `primaryColor` | Overrides the configured button colour |

### `Zudo.reset()`

Forgets who is signed in. Call it on sign-out if one browser can sign in as more than one person — otherwise the next
identify merges the new person's traits onto the previous one's ids.

## Using it with Segment

If your product already calls `analytics.identify` and `analytics.group`, you don't need the identify calls:

```js theme={null}
Zudo.init("zjs_pub_...", { autoLoadSegment: true });
Zudo.nps("survey");
```

zudo.js listens for those calls and takes identity from them. Your own Segment calls still run exactly as before.

## What the survey looks like

The panel renders inside a shadow root, so your product's CSS cannot reach it and its styles cannot leak onto your
page. It has no external stylesheet and loads no fonts, so it appears without a reflow. You control the colour,
position, delay and every piece of wording from **Settings › NPS survey**.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Settings says Zudo has not heard from the key">
    Open your browser's network tab and look for a request to `zudo.so/api/zudo-js/v1/identify`.

    * **No request at all** — the script tag is missing, or `Zudo.init` was never called. Check the browser console for a
      `[zudo.js]` warning.
    * **401** — the key is wrong, or your **Allowed origins** list does not include the origin the page is served from.
      List the exact origin, scheme included: `https://app.example.com`.
    * **400** — the call carried no `userId`, `accountId` or `organizationId`.
  </Accordion>

  <Accordion title="The survey never appears">
    Call `Zudo.nps("test")` in your browser console. It ignores every rule and records nothing, so if the panel appears
    your install is fine and the answer is one of the rules. The console prints which one.

    The usual causes, in order: the survey is switched off, the account is younger than the minimum age (see the
    `createdAt` warning above), or this person has not used the product on enough separate days yet.
  </Accordion>

  <Accordion title="Events arrive but land on no account">
    The `accountId` you send has to match an account's **External ID** in Zudo. Check one on the account page, or
    turn on [auto-create](/integrations/event-auto-create) and let Zudo make the account the first time it sees the id.
  </Accordion>

  <Accordion title="A content-security policy blocks the script">
    Add `https://zudo.so` to your `script-src` and `connect-src` directives. zudo.js loads no other hosts and no fonts.
  </Accordion>
</AccordionGroup>

## Limits

* One `track` call may carry up to 50 events.
* Traits must be a string, number, boolean, date or a flat list of those. Nested objects are dropped.
* The survey's wording comes from your Zudo settings, not from your page, so changing it takes effect on the next
  session with no redeploy of yours.
