Skip to content

Securely Connect Visitors

By default, everyone who chats through your widget is anonymous. Hope Chat can tell one browser from another, but it has no idea who the person behind it is, so they show up in your inbox as Web visitor with a short id.

If your website has accounts and people log in, Securely connect lets your backend vouch for them. Your server signs a small token with a secret only it knows, the widget passes that token to Hope Chat, and Hope Chat verifies the signature before trusting a single field. Nobody can pretend to be one of your users by editing the page.

What you get

  • Real contacts. The visitor's name, email, and phone (whatever you choose to send) land on their contact record in Engage.
  • One conversation everywhere. A verified visitor is the same person on their laptop, their phone, and after clearing cookies. They pick up the conversation where they left off.
  • History carries over on the first sign-in. If someone chatted anonymously and then logged in for the first time, that earlier conversation moves onto their verified identity - unless the anonymous contact already carries a name, email, or phone that the token contradicts, which means someone else used this browser and their transcript stays with them. Later anonymous chats (a second browser, or after signing out) stay separate - once a verified identity exists, Hope Chat cannot tell whether the anonymous history on that browser belongs to the same person.
  • No spoofing. Only your backend has the secret, so a visitor cannot claim to be someone else.

Before you start

This is for a Widget chat - create and install one first (see Add a Chat Widget to Your Website). You also need a backend that can sign a JWT: every language has a library for it, and the examples below cover Node.js and PHP.

Hosted Web page chats do not support Securely connect.

1. Get your client ID and identity secret

Open your widget in the editor and find the Securely connect section. It has two values, each with a copy button:

ValueWho may see it
Client IDPublic. It is the id already in your embed snippet.
Identity secretPrivate. It is the signing key, and anyone who has it can impersonate any of your users. Backend only.

Keep the secret on your server

Store the identity secret the way you store a database password: in your server's environment variables or secret manager. Never put it in front-end JavaScript, in a mobile app, or in a public code repository. If it ever leaks, rotate it.

2. Sign a token on your backend

When you render a page for a logged-in user, have your backend sign a JWT with the identity secret and hand it to the page.

The token must be signed with HS256. Other algorithms (including none) are rejected.

The payload

ClaimRequiredTypeLimitWhat it is
userIdYesstring128 charsThe user's id on your platform. This is what identifies them.
emailNostring200 charsSaved on the contact in Engage.
nameNostring200 charsShown as the contact's name instead of "Web visitor".
phoneNostring32 charsSaved on the contact in Engage.
expRecommendednumber (unix seconds)When the token stops being accepted. Standard JWT expiry.

Claim names are camelCase, exactly as above. Anything else you put in the payload is ignored, and the whole token must stay under 4096 characters.

No stable user id?

Use the person's email address as userId. It works as long as it is unique in your system and does not change - if a user can change their email, prefer your internal database id, otherwise they turn into a new contact.

Node.js

js
import jwt from 'jsonwebtoken';

const userJwt = jwt.sign(
  {
    userId: user.id,       // required
    email: user.email,     // optional
    name: user.fullName,   // optional
    phone: user.phone,     // optional
  },
  process.env.HOPECHAT_IDENTITY_SECRET,
  { algorithm: 'HS256', expiresIn: '1h' },
);

PHP

php
use Firebase\JWT\JWT;

$userJwt = JWT::encode(
    [
        'userId' => (string) $user->id,   // required
        'email'  => $user->email,         // optional
        'name'   => $user->name,          // optional
        'phone'  => $user->phone,         // optional
        'exp'    => time() + 3600,
    ],
    getenv('HOPECHAT_IDENTITY_SECRET'),
    'HS256'
);

3. Pass the token to the widget

Add the signed token to the init call in your embed snippet, next to the id that is already there:

html
<script>
  (function(w,d,s){w.HopeChat=w.HopeChat||function(){(w.HopeChat.q=w.HopeChat.q||[]).push(arguments)};
  var j=d.createElement(s);j.async=1;j.src='https://widget.hope.chat/widget.js';d.head.appendChild(j);})(window,document,'script');
  HopeChat('init', { id: 'egpa:2PitDOAL1Ie', userJwt: 'HERE_GOES_THE_SIGNED_TOKEN' });
</script>

Mint a fresh token every time you render the page, and keep exp short (an hour is plenty). The token itself does reach the browser, so treat it like a session cookie - but the secret that signs it never leaves your server.

For visitors who are not logged in, leave userJwt out entirely. The widget works exactly as before and those people stay anonymous. Both kinds of visitors can use the same widget at the same time.

Single-page apps: identify after login

If your site logs people in without a full page reload, the widget is already running by the time you have a token. Call update as soon as your login request comes back:

js
// right after your own login call resolves
HopeChat('update', { userJwt: tokenFromYourBackend });

The chat re-identifies in place: the visitor keeps the panel open, and their earlier anonymous messages come along on their first sign-in (same rule as above). Calling update before init does nothing (the widget logs a warning).

When they sign out, hand the widget an empty token in the same way:

js
// right after your own logout call resolves
HopeChat('update', { userJwt: null });

The chat drops the signed-in session on the spot and comes back anonymous, with the signed-in conversation no longer readable - which matters most on shared computers, where the next person is using the same browser. A full page reload without a userJwt does the same thing, so you only need this call if your sign out keeps the page alive.

When a token is rejected

A bad token never breaks the chat. The widget still loads and the visitor can still write to you, they are simply anonymous for that session, and no data from the token is saved.

Hope Chat rejects a token when:

  • it was signed with a different secret (or the secret was rotated);
  • it was signed with an algorithm other than HS256;
  • its exp has passed;
  • userId is missing, empty, or longer than 128 characters;
  • one of the optional fields is not a string or is over its length limit;
  • it is malformed, or you sent it to a hosted Web page chat, which has no identity secret.

When that happens the widget writes a warning to the browser console:

[HopeChat] the userJwt was rejected (bad signature, expired, or missing "userId") -
this visitor is chatting anonymously. Sign the token with this widget's
identity secret from the engage widget editor.

So if your verified visitors keep showing up as "Web visitor", open the browser's developer console on your own site and look for that line. Most of the time it is a secret that does not match the widget you are embedding, or a token that was minted long before the page was served.

Rotating the identity secret

Click Rotate secret in the Securely connect section to replace the secret with a new one. Use it if the old one may have leaked, or as routine hygiene.

Rotation takes effect immediately:

  • Tokens signed with the old secret stop being accepted, so update your backend in the same maintenance window.
  • Anyone already chatting is not kicked out, and no conversation or contact is lost. Only the next page load with an old token drops back to anonymous.

Good to know

  • Identities are per chat. The same userId on two different widgets is two different contacts. Hope Chat never merges people across your chats.
  • Only the listed claims are used. Extra claims in the payload are ignored, so do not rely on them, and do not put anything sensitive in the token: the browser can read it.
  • Anonymous visitors keep working. Verification is an option per visitor, not a wall. Nameless visitors appear as "Web visitor" plus a short id in your Engage inbox and contacts.
  • Replies land in the same place. Verified or not, every conversation shows up in your Engage inbox / Interaction Feed.