Skip to content

Web SDK

Last updated

@whisperr/web sends identify calls and product events from the browser to Whisperr. Use it in any JavaScript app. For React and Next.js, use the React or Next.js package, which wraps this one.

npm i @whisperr/web

The package is @whisperr/web on npm. The current version is 0.4.0. It has no runtime dependencies.

whisperr.ts
import { Whisperr } from "@whisperr/web";
export const whisperr = Whisperr.init({
apiKey: "wpk_your_publishable_key",
appVersion: "2.4.0", // optional: your release, sent as context.app_version
});
// After sign-in, and when you restore a signed-in session:
whisperr.identify(user.id, {
traits: { first_name: user.firstName, plan: user.plan },
email: user.email,
emailOptedIn: user.agreedToEmail, // the user's answer in your app; leave it out when the app does not ask
});
// When the user picks a language in your product:
whisperr.setAppLanguage("pt-BR");
// When the user does something in your product. The name is an example:
// use your app's own action.
whisperr.track("report_exported", { format: "pdf" });
// On sign-out:
whisperr.reset();

Whisperr.init() creates one client per page and returns the same client on later calls. It also sets window.whisperr.

Event names must be lowercase snake_case. The Web SDK drops any other name before it queues the event and reports it to onError as dropped.

Use a publishable key (wpk_…). In the dashboard it is an App key under Settings → API keys. It ships in your bundle by design, and anyone can read it. So it cannot change a user’s existing email address or phone number. Whether an email address it sends is opted in depends on your answer to Does your signup get permission to email users? See How do contact channels and consent work in the Web SDK?

Never put a server key (wrk_…) in browser code. Keep it in your backend secrets and use it with a server SDK.

Before identify(), the Web SDK sends events right away under an anonymous id (a UUID v4 kept in localStorage). identify() sends that anonymous id with the user id, and Whisperr moves the anonymous visitor’s events and state to the user. reset() clears the user and starts a new anonymous visitor.

How does the Web SDK identify returning users?

Section titled “How does the Web SDK identify returning users?”

When a user id is stored from an earlier visit, the Web SDK sends one identify for that user about 2 seconds after page load. It carries the browser timezone and locale and the stored app language, never contact channels. The Web SDK skips it when your app calls identify() first, and when the same identify was delivered less than 24 hours ago.

Users who signed in before you installed the SDK have no stored id. Call identify() when you restore a session so Whisperr knows them.

Call setAppLanguage(tag) when the user picks a language in your product. Pass a BCP 47 tag such as de or pt-BR; _ becomes -.

  • The Web SDK stores the value and sends it as traits.app_language: for the current user, or for the anonymous visitor before sign-in.
  • Every later identify carries it. A traits.app_language you pass to identify() wins.
  • Calling it again with the same value sends nothing, so you can call it on every page load.
  • setAppLanguage(null) clears the stored value. Whisperr keeps the last value it received.
  • An invalid tag is ignored (logged when debug is on).

Whisperr writes a message in traits.app_language first, then in the context.locale of the event that triggered it, then in traits.locale.

What the Web SDK adds to identify calls and events

Section titled “What the Web SDK adds to identify calls and events”

On every identify(), the Web SDK fills two traits from the browser unless you pass them yourself:

TraitSource
timezoneIANA name from Intl.DateTimeFormat().resolvedOptions().timeZone
localenavigator.language

Every event’s context carries:

KeyValue
platformweb
sdk_namewhisperr-web
sdk_versionThe SDK version, for example 0.4.0
app_version / app_buildThe appVersion / appBuild options. Not set: no key.
pageurl, path, referrer, title
session_id, user_agent, librarySession and browser details

The Web SDK never sets context.locale, because Whisperr reads it as the message language for that one event. Pass it in the third argument of track() only when you mean that.

Section titled “How do contact channels and consent work in the Web SDK?”

You can send an email address from the browser with identify(), either in channels or with the email shortcut (with emailOptedIn for the user’s choice). The Web SDK runs with the publishable key, so Whisperr ignores verified: true from it.

What Whisperr does with the address depends on your answer to Does your signup get permission to email users? With Yes, the address is opted in unless the app sends optedIn: false. With Per user, only when the app sends optedIn: true. With No, or when Require a server key is on, Whisperr stores it as pending until your backend confirms it with a server SDK. optedIn: false always records an opt-out. An earlier opt-out, including the unsubscribe link and bounce or complaint suppression, is kept. A later identify from the app never opts the address back in.

App-side consent needs @whisperr/web, @whisperr/react and @whisperr/next 0.4.0 or later. Older versions cannot set it: Whisperr stores the address as pending, whatever your answer. Older versions also send opted_in: true on the email shortcut by default. See Contact consent for the limits on addresses from the app.

  • The queue lives in localStorage, so events survive a reload. In browsers with the Web Locks API, only one tab sends the shared queue at a time.
  • Track events go to POST /v1/events/batch; identify calls go to POST /v1/identify, in the order you made them.
  • When the page is hidden or unloaded, the Web SDK sends what fits in the browser’s keepalive limit with fetch and keepalive. Events stay queued until Whisperr confirms them, so a lost request is resent on the next load. Whisperr removes duplicates by $message_id.
  • 429, 5xx and network errors are retried with backoff. A Retry-After header on 429 or 503 is honored, up to 60 seconds.
  • On 401 or 403 the Web SDK stops sending and keeps the queue.
  • Pageviews are captured as page_viewed on load and on History API navigation. Turn this off with autocapturePageviews: false.

See Delivery, retries, idempotency for the full contract.

whisperr.optOut() stops all capture, clears the queue and stores the choice in localStorage. whisperr.optIn() turns capture back on. Set respectDoNotTrack: true to capture nothing when the browser sends Do Not Track.

Paste this before </head>. The stub queues calls until the script loads.

index.html
<script>
!function(){var w=window.whisperr=window.whisperr||[];w._opts={};w.load=function(k,o){w._key=k;w._opts=o||{};var s=document.createElement("script");s.async=1;s.src="https://cdn.jsdelivr.net/npm/@whisperr/web@0.4.0/dist/whisperr.global.js";document.head.appendChild(s)};["identify","track","page","setAppLanguage","flush","reset","optIn","optOut"].forEach(function(m){w[m]=function(){w.push([m].concat([].slice.call(arguments)))}})}();
whisperr.load("wpk_your_publishable_key");
</script>

Then call whisperr.identify(…) and whisperr.track(…) anywhere on the page. The second argument of whisperr.load() takes the options below.

MethodWhat it does
identify(userId, { traits, channels, email, emailOptedIn, phone, pushToken, preferredChannel })Sets the current user and sends their traits and contacts. emailOptedIn is the user’s email choice for the email shortcut. channels overrides the shortcuts.
track(eventType, properties?, context?)Queues one event for the current user or the anonymous visitor.
page(name?, properties?)Sends a page_viewed event.
setAppLanguage(tag | null)Stores the language the user picked in your product.
flush()Sends everything queued. Returns a promise.
reset()Clears the user and starts a new anonymous visitor.
optIn() / optOut()Turns capture on or off for this browser.
OptionDefaultNotes
apiKey—Publishable key (wpk_…). Required.
baseUrlhttps://api.whisperr.netIngestion base URL.
appVersion / appBuild—Sent as context.app_version / context.app_build.
flushAt20Send when this many events are queued.
flushIntervalMs10000Send at least this often (ms).
maxQueueSize1000Oldest events drop when the queue is full.
maxBatchSize500Events per batch request. 500 is the maximum.
maxRetries6Retries before a send pass stops. Events stay queued.
requestTimeoutMs10000Per-request timeout (ms).
autocapturePageviewstrueCaptures page_viewed on load and navigation.
respectDoNotTrackfalseCaptures nothing when Do Not Track is on.
persistence"localStorage"Or "memory".
disabledfalseCaptures and sends nothing.
debugfalseLogs warnings to the console.
onError—(error) => void. error.type is auth, dropped or retry_exhausted.

Common questions

Which API key does @whisperr/web use?

The publishable key (wpk_…), shown as an App key in Settings → API keys. It ships in your bundle by design. Never put the server key (wrk_…) in browser code.

Are events tracked before login lost?

No. Before identify() the Web SDK sends events under an anonymous id. When you call identify(), it sends the same anonymous id, and Whisperr moves that visitor's events and state to the user.

Does the Web SDK use cookies?

No. It stores its queue, the anonymous id and the user id in localStorage. When localStorage is unavailable, for example in some private modes, it falls back to memory.

Can the Web SDK opt a user in to email?

Yes, when you answered Yes or Per user to Does your signup get permission to email users?, Require a server key is off, and the app uses @whisperr/web 0.4.0 or later. With Yes, the address is opted in unless the app sends emailOptedIn: false. With Per user, only when it sends emailOptedIn: true. With No, Whisperr stores it as pending until your backend confirms it with a server SDK.

Is @whisperr/web safe to import during server-side rendering?

Yes. Outside a browser, Whisperr.init() returns a client that captures and sends nothing.