Skip to main content
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. It is about 5 KB gzipped, has no dependencies, and loads deferred so it never blocks your page.
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.

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

1

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, with Copy snippet to copy it.
The Install zudo.js section of Settings › NPS survey: the snippet with the publishable key in it, the Copy snippet button, and when Zudo last heard from the key
2

Paste the snippet

Add it before the closing </body> tag, and replace the placeholders with your own values.
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.
3

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.
4

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.

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.

Zudo.account(payload)

The account the signed-in person belongs to.
accountId should be the id you already use for that customer. Zudo matches it against an existing account’s External ID first, then its Vitally ID — so an account that is already in Zudo, however it got there, is the one your events land on. Nothing is duplicated. If no account matches, Zudo can create one; see Auto-creating accounts and contacts. Traits you send become Zudo traits on the account, usable in segments, playbooks and health scoring like any other. Account traits and user traits are kept apart: what you send here goes on the account, and what you send to Zudo.user goes on the person.
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.

Zudo.user(payload)

The signed-in person.
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 matches the person to an existing contact by userId first and then by email. The email fallback is the one that matters in practice: most of your contacts are already in Zudo from a CRM sync or a shared inbox, under an id your product has never heard of. Send the email and they resolve to that contact rather than becoming a second copy of the same person.

Zudo.organization(payload)

If your product has a layer above accounts, name it here.
An organization becomes a parent account in Zudo, and the accounts you name alongside it become its members. That is Zudo’s own structure for “one customer is several accounts”, so a CSM reads the organization’s rolled-up MRR, health and NPS on one page instead of comparing three. A few things worth knowing:
  • The relationship is applied on the next aggregation run after both accounts exist, so on a brand new install it appears a few minutes after the accounts do.
  • Zudo allows one level. An organization inside another organization is not applied.
  • If somebody has already put an account into a different parent account in Zudo, that placement wins. A deliberate choice is not overridden by an inbound event.
  • Traits you send here go on the parent account, not on its members.
If you would rather your organizations stayed flat accounts, this can be switched off per connection — ask support.

Zudo.track(payload)

Something the person did.
Events are batched and sent together, and flushed when the page closes. They are ordinary product events — the same pipeline, the same table and the same rules as the Segment and PostHog integrations. They arrive in the account timeline and the daily counts chart, feed indicators, and count toward health scoring if you have it weighted. Only events on your allowlist are rolled up; see Product events for the allowlist and the discovered-events picker. An event with no accountId still attributes, as long as Zudo has seen a Zudo.user call for that userId — the user-to-account link is remembered, exactly as it is for a Segment group call. Properties are the event’s, not the account’s. A track call never writes traits; use Zudo.account or Zudo.user for that.

Zudo.nps(mode?, options?)

Shows the NPS survey, if this person is due one. See NPS surveys for the rules and the wording.

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:
zudo.js listens for those calls and takes identity from them. Your own Segment calls still run exactly as before.

Styling the survey

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. Colour, position, delay and every piece of wording are set from Settings › NPS survey. For anything further, that screen has a Custom CSS box: whatever you put there is added after Zudo’s own rules, inside the shadow root, so your rule wins at equal specificity without !important. The preview beside it renders the real panel with your CSS applied, and you can flip it between the three steps. Every element carries a stable class name. These are a contract — we add to this list, we don’t rename what is on it:
Because the panel is in a shadow root, your CSS here only ever affects the survey. It cannot style the rest of your product, and the rest of your product cannot style it.

Troubleshooting

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. The library now says so in the console and prints the first and last characters of what it sent, which is usually enough to spot a key that picked up a stray quote on its way through a secret store or a template. List the exact origin, scheme included: https://app.example.com.
  • 400 — the call carried no userId, accountId or organizationId.
The key belongs to an organization, and that is the organization the survey is read from. Minting a key while you are in a different organization than the one you configured the survey in gives you a key that reports somewhere with no survey on it — while the settings screen you were looking at stays, correctly, switched on.Open Settings › Connections in the organization you want the answers in, expand the zudo.js connection, and copy the publishable key shown there.
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.
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 and let Zudo make the account the first time it sees the id.
Add https://zudo.so to your script-src and connect-src directives. zudo.js loads no other hosts and no fonts.

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.
  • Custom CSS is capped at 20,000 characters.
  • The survey’s wording and styling come from your Zudo settings, not from your page, so changing either takes effect on the next session with no redeploy of yours.