Install

React

Mount the loader once and connect signed-in users.

Anonymous install

Load the script in a component that renders once. Call update after every navigation.

src/App.tsx or root layout
// Add once in your root component
useEffect(() => {
  const script = document.createElement("script");
  script.src = "https://whazzup.io/widget.js";
  script.dataset.workspace = "YOUR_WORKSPACE_KEY";
  script.async = true;
  document.body.appendChild(script);
  return () => script.remove();
}, []);

Authenticated install

1. Install with user data — WhazzupWidget.tsx

Mount once near your application root. The widget reads account changes automatically; logout is explicit.

import { useEffect } from "react";

type User = { id: string; name: string; email: string } | null;

export function WhazzupWidget({ user }: { user: User }) {
  useEffect(() => {
    window.whazzupSettings = user
      ? { name: user.name, email: user.email, userId: user.id }
      : null;
    if (!user) window.whazzup?.("logout");
  }, [user]);

  useEffect(() => {
    const script = document.createElement("script");
    script.src = "https://whazzup.io/widget.js";
    script.dataset.workspace = "YOUR_WORKSPACE_KEY";
    script.async = true;
    document.body.appendChild(script);
    return () => script.remove();
  }, []);

  return null;
}

2. Verified identity (recommended) — GET /api/whazzup-identity

Add this logic to your existing authenticated server endpoint. Never send the secret to React.

// Server only — never expose WHAZZUP_IDENTITY_SECRET in the browser
import { createHmac } from 'node:crypto';

const payload = {
  aud: 'YOUR_WORKSPACE_KEY',
  sub: user.id,
  exp: Math.floor(Date.now() / 1000) + 300,
  jti: crypto.randomUUID(),
  name: user.name,
  email: user.email,
  attributes: { plan: user.plan }
};
const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
const signature = createHmac('sha256', process.env.WHAZZUP_IDENTITY_SECRET)
  .update(body).digest('base64url');
return body + '.' + signature;

3. Browser — pass the signed token

Mount once near your application root. The endpoint must return { token } for the current signed-in user. Fetch a fresh token on every full page load and after expiry (5 minutes, single-use) — otherwise people stay marked as unverified.

import { useEffect } from "react";

declare global { interface Window { whazzup: (...args: unknown[]) => void } }

export function WhazzupWidget({ signedIn }: { signedIn: boolean }) {
  const identify = () => fetch("/api/whazzup-identity")
    .then((response) => response.json())
    .then(({ token }) => window.whazzup("identify", { token }));

  useEffect(() => {
    const script = document.createElement("script");
    script.src = "https://whazzup.io/widget.js";
    script.dataset.workspace = "YOUR_WORKSPACE_KEY";
    script.async = true;
    script.onload = () => { if (signedIn) void identify(); };
    document.body.appendChild(script);
    return () => { script.remove(); };
  }, []);

  useEffect(() => {
    if (!signedIn) { window.whazzup?.("logout"); return; }
    if (window.whazzup) void identify();
  }, [signedIn]);

  return null;
}

Lifecycle

Mount the loader once. Identify after login, use update after meaningful navigation changes, and call shutdown before another account uses the same browser tab.