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.
Install
Section titled “Install”npm i @whisperr/webThe package is @whisperr/web
on npm. The current version is 0.4.0. It has no runtime dependencies.
Quickstart
Section titled “Quickstart”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.
Which key goes in the browser?
Section titled “Which key goes in the browser?”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.
What happens before the user signs in?
Section titled “What happens before the user signs in?”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.
How do I set the message language?
Section titled “How do I set the message language?”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_languageyou pass toidentify()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
debugis 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:
| Trait | Source |
|---|---|
timezone | IANA name from Intl.DateTimeFormat().resolvedOptions().timeZone |
locale | navigator.language |
Every event’s context carries:
| Key | Value |
|---|---|
platform | web |
sdk_name | whisperr-web |
sdk_version | The SDK version, for example 0.4.0 |
app_version / app_build | The appVersion / appBuild options. Not set: no key. |
page | url, path, referrer, title |
session_id, user_agent, library | Session 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.
How do contact channels and consent work in the Web SDK?
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.
How does the Web SDK deliver events?
Section titled “How does the Web SDK deliver events?”- 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 toPOST /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
fetchandkeepalive. Events stay queued until Whisperr confirms them, so a lost request is resent on the next load. Whisperr removes duplicates by$message_id. 429,5xxand network errors are retried with backoff. ARetry-Afterheader on429or503is honored, up to 60 seconds.- On
401or403the Web SDK stops sending and keeps the queue. - Pageviews are captured as
page_viewedon load and on History API navigation. Turn this off withautocapturePageviews: false.
See Delivery, retries, idempotency for the full contract.
How do I let users opt out of tracking?
Section titled “How do I let users opt out of tracking?”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.
Script tag (no build step)
Section titled “Script tag (no build step)”Paste this before </head>. The stub queues calls until the script loads.
<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.
Methods
Section titled “Methods”| Method | What 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. |
Options
Section titled “Options”| Option | Default | Notes |
|---|---|---|
apiKey | — | Publishable key (wpk_…). Required. |
baseUrl | https://api.whisperr.net | Ingestion base URL. |
appVersion / appBuild | — | Sent as context.app_version / context.app_build. |
flushAt | 20 | Send when this many events are queued. |
flushIntervalMs | 10000 | Send at least this often (ms). |
maxQueueSize | 1000 | Oldest events drop when the queue is full. |
maxBatchSize | 500 | Events per batch request. 500 is the maximum. |
maxRetries | 6 | Retries before a send pass stops. Events stay queued. |
requestTimeoutMs | 10000 | Per-request timeout (ms). |
autocapturePageviews | true | Captures page_viewed on load and navigation. |
respectDoNotTrack | false | Captures nothing when Do Not Track is on. |
persistence | "localStorage" | Or "memory". |
disabled | false | Captures and sends nothing. |
debug | false | Logs 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.