Guides

Install Customer messenger

Messenger runs inside your product for signed-in users, whose identity your server signs, and can also run on public pages for anonymous website visitors. In your workspace, Settings → Customer messenger → Installation shows this guide with your public key already filled in.

Before you start

  • Signed-in users, and optionally visitors

    In your product, your server signs who each user is. On public pages such as a marketing site, turn on anonymous visitors under Experience and skip the server step.

  • Trusted website origins

    Add every origin that will load Messenger, such as https://app.acme.com and http://localhost:3000.

  • Two server settings

    RIDGETOP_WIDGET_SECRET is the signing secret. RIDGETOP_MESSENGER_ORIGIN is the origin of the page that loads Messenger. Keep both out of browser code.

Step 1: Create a signed identity endpoint

Your server tells Messenger who the signed-in user is, signed so the browser cannot change it. Serve it from your product's own domain, behind your normal login.

Express route, behind your login middlewarejavascript
import { createHmac } from "node:crypto";

// requireUser: your existing middleware. It must answer 401 (not a redirect)
// when nobody is signed in.
app.get("/api/support-identity", requireUser, (req, res) => {
  const user = req.user;
  const payload = JSON.stringify({
    id: String(user.id),
    email: user.email,
    name: user.name,
    // Optional. Leave it out for users without an organization.
    company: user.organization
      ? { id: String(user.organization.id), name: user.organization.name }
      : undefined,
    // The exact origin of the page that loads Messenger.
    origin: process.env.RIDGETOP_MESSENGER_ORIGIN,
    iat: Math.floor(Date.now() / 1000), // seconds, not milliseconds
  });

  // Sign the payload string itself, then send that same string.
  const signature = createHmac("sha256", process.env.RIDGETOP_WIDGET_SECRET)
    .update(payload)
    .digest("hex");

  res.set("Cache-Control", "no-store");
  res.json({ payload, signature });
});
  • Sign the exact string you send. Serialize the JSON once, sign it, and return that string unchanged. Re-encoding it on the way out breaks the signature.
  • Use the secret as text. HMAC-SHA256 keyed with RIDGETOP_WIDGET_SECRET exactly as shown, hex-encoded. Do not hex-decode the secret first.
  • Sign the page's origin, not the endpoint's. Set RIDGETOP_MESSENGER_ORIGIN to the origin where Messenger loads, such as https://app.acme.com. It must match a trusted origin exactly.
  • Fresh, in seconds, never cached. iat is Unix time in seconds and expires after 5 minutes. Respond with Cache-Control: no-store, so no proxy or CDN ever serves one user's identity to another.
  • 401 when signed out. Messenger is for signed-in users. Return 401 rather than a redirect, so the browser code can skip Messenger cleanly.
  • company is optional. Send it only for users who belong to an organization, and include both a stable id and a name.

Step 2: Load Messenger in your frontend

Load widget.js from Ridgetop with a script tag, start Messenger once the user is signed in, and shut it down when they sign out so the next person on the device never sees their conversations.

Pages your signed-in users see, before </body>html
<script src="https://staging.ridgetop.io/widget.js"></script>
<script>
  async function fetchIdentity() {
    const response = await fetch("/api/support-identity", {
      headers: { Accept: "application/json" },
    });
    if (!response.ok) throw new Error(`Support identity failed: ${response.status}`);
    return response.json(); // { payload, signature }
  }

  // identity is a function so Messenger can renew the session on long visits.
  Ridgetop.boot({
    publicKey: "wpk_your_public_key",
    identity: fetchIdentity,
  });

  // When the user signs out:
  // Ridgetop.shutdown();
</script>

Using another framework? Follow the same pattern: load the script once, call Ridgetop.boot after sign-in with identity as a function, and call Ridgetop.shutdown() on sign-out. Single-page navigation is tracked automatically.

One site with both signed-out and signed-in pages? With anonymous visitors on, have your identity function return null when the endpoint answers 401. Messenger continues as a visitor, and their conversations move to their account when they sign in.

Step 3: Add properties, events, and consentOptional

Enrich conversations with user and company properties, record product events, and respect analytics consent.

Anywhere after widget.js has loadedjavascript
Ridgetop.boot({
  publicKey: "wpk_your_public_key",
  identity: fetchIdentity,

  // Optional properties. Keys must match API keys in Settings → Properties.
  user: { properties: { plan_tier: "growth" } },
  company: { properties: { employee_count: 42 } },

  // false keeps product analytics and replay off for this visitor,
  // for example until they accept your cookie banner.
  analytics: visitorAcceptedAnalytics,
});

// Product events. Calls made before boot finishes are queued.
Ridgetop.track("project.created", { plan: "growth" });

// Open or close Messenger from your own "Contact support" button.
Ridgetop.show();
Ridgetop.hide();

Step 4: Check the connection

Open a page with Messenger installed while signed in. The browser console explains any problem with a Ridgetop: message.

Console: "the signed origin … does not match this page's origin"
Set RIDGETOP_MESSENGER_ORIGIN to the exact origin of the page, including scheme and port, with no trailing slash.
Console: "this page's origin is not in Trusted website origins"
Add the origin under Identity & security → Trusted website origins, and save.
Console: "the signature does not match"
Check that the server uses the current signing secret, and returns the same payload string it signed.
Console: "the signed identity is more than 5 minutes old"
Create the identity on every request, with iat in seconds, and make sure the endpoint is not cached.
Console: "load widget.js with a <script> tag"
widget.js was bundled or inlined. Load it from your Ridgetop URL with a script tag instead.
Console: "anonymous visitors are turned off"
A page booted Messenger without an identity. Turn on anonymous visitors under Experience, or pass identity on that page.
No launcher appears
The launcher shows only once a session starts. Check the console for a Ridgetop: message explaining why it did not.
Ridgetop is not defined
boot ran before widget.js finished loading. Call it from the script's onload, or onReady in Next.js.