Hourzero

Identity verification

Recognize visitors who are signed in to your website, so their chats follow them across devices and your actions can act for them.

When a customer is signed in to your website, your agent can know who they are. Your server signs a short token for the signed-in user, and your page hands it to the chat. Hourzero checks the token and links the visitor to one contact, whatever browser or device they use.

Where to find it: open your agent and go to Playground. Under Preview channel, choose Chat Bubble, Center Stage, or Chat Iframe, select Publish, and look for Identity verification in the dialog.

What you get

  • One contact per customer. Every device where they sign in links to the same contact, with their name, email, and phone in Contacts, Conversations, and the Helpdesk.
  • History that follows them. A customer who signs in on their phone sees the conversations they had on their laptop.
  • An agent that knows who it's talking to. It doesn't ask for an email it already has, and forms fill in their details.
  • Actions that act for them. Your actions can look up this customer's orders or balance without asking for an account number. See Use the signed-in user in actions.

Identity verification is included on every plan. It works in Chat Bubble, Center Stage, and Chat Iframe installed with Script. Chat Iframe installed as Iframe only, share links, and messaging apps stay as they are.

How it works

  1. 1GenerateCreate the agent's identity secret in the publish dialog.
  2. 2SignYour server signs a token for the signed-in user with the secret.
  3. 3IdentifyYour page passes the token to the chat.
  4. 4VerifyHourzero checks the token and links the visitor to their contact.

Your page can't simply tell the chat an email address: anyone could type someone else's email into their browser and read that person's conversations. The signature proves the details came from your server.

Step 1: Generate the secret

  1. In the Playground, open a website channel and select Publish, then Website widget or Chat Iframe.
  2. Under Identity verification, select Generate secret.
  3. Copy the secret and store it on your server, for example as HOURZERO_IDENTITY_SECRET. Select I saved it.

One secret covers all of the agent's website channels. Only workspace owners and admins can generate, reveal, roll, or turn off the secret.

Keep the secret on your server

Anyone with the secret can sign in to your agent's chat as any of your users. Never put it in browser code, a mobile app, or a public repository.

Step 2: Sign a token on your server

When a signed-in user loads a page with the chat, your server creates a JSON Web Token (JWT) for them, signed with the secret using HS256.

ClaimRequiredWhat it's for
user_idYesYour own ID for this user. sub works too. Up to 256 characters.
emailNoShown on the contact, and used for forms and tickets.
nameNoShown on the contact and to your team.
phonenumberNoShown on the contact. phone works too.
custom_attributesNoYour own details, such as { "plan": "pro", "account_id": "acc_42" }. Up to 50 keys and 4 KB.
expNoWhen the token expires. We recommend an hour; keep it under 24 hours.

Each time a token arrives, the contact is updated: a detail in the token is saved, a detail you leave out stays as it was, and null clears it. The same goes for each key in custom_attributes.

In Node.js, with the jose package:

import { SignJWT } from "jose";

const secret = new TextEncoder().encode(process.env.HOURZERO_IDENTITY_SECRET);

export function hourzeroToken(user) {
  return new SignJWT({
    user_id: user.id,
    email: user.email,
    name: user.name,
    custom_attributes: { plan: user.plan },
  })
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime("1h")
    .sign(secret);
}

In Python, with PyJWT:

import os
import time

import jwt

def hourzero_token(user):
    return jwt.encode(
        {
            "user_id": str(user.id),
            "email": user.email,
            "name": user.name,
            "exp": int(time.time()) + 3600,
        },
        os.environ["HOURZERO_IDENTITY_SECRET"],
        algorithm="HS256",
    )

Tokens are signed, not encrypted: the visitor can read what's in them. Only include details they may see.

Step 3: Identify the visitor on your page

The page needs the Hourzero script: the website widget code, or Chat Iframe installed with Script. After the user signs in, pass the token from your server:

window.hourzero("identify", { token: "TOKEN_FROM_YOUR_SERVER" });

If your page renders the token before the script loads, set it first instead:

<script>
  window.hourzeroUserConfig = { token: "TOKEN_FROM_YOUR_SERVER" };
</script>

When the user signs out, reset the chat:

window.hourzero("resetUser");

Some things to know:

  • One call identifies every Hourzero chat on the page. If the page shows chats from more than one agent, see Pages with more than one agent.
  • Only the token is read. Anything else you pass next to it is ignored, and the browser's developer console shows a warning.
  • When a token expires, the chat shows "Your sign-in to this website ended. Refresh the page to keep chatting." Your page can sign the user in again without a reload by listening for the hourzero:identity-error event:
window.addEventListener("hourzero:identity-error", async () => {
  const token = await fetch("/api/hourzero-token").then((r) => r.text());
  window.hourzero("identify", { token });
});

Pages with more than one agent

Each agent has its own secret, so a token signed for one agent is refused by another. When a page shows chats from two agents, for example Acme Support's chat bubble and Acme Sales' Chat Iframe, sign a token for each and pass the agent's public ID, the data-agent-id from its embed code:

window.hourzero("identify", { token: supportToken, agentId: "acme-support" });
window.hourzero("identify", { token: salesToken, agentId: "acme-sales" });
  • A token with an agentId reaches only that agent's chats, and wins over a token set without one.
  • window.hourzero("resetUser", { agentId: "acme-sales" }) signs out only that agent's chats. window.hourzero("resetUser") with no agentId signs out every chat on the page.
  • window.hourzeroUserConfig takes an agentId too.

When visitors sign in and out

  • Signing in mid-chat: the conversation open in that browser joins their account. Earlier anonymous chats in that browser stay with the browser.
  • Signing out: after resetUser, the chat starts again as a new anonymous visitor, so the next person on a shared computer sees nothing.
  • Switching users: works like signing out and in.
  • Each channel keeps its own history. A customer's Chat Bubble conversations don't appear in Chat Iframe, though both belong to the same contact.
  • Each agent keeps its own contacts and secret. Two agents on the same website don't share contacts.

Use the signed-in user in actions

Fill inputs from the signed-in user

In a custom action that makes a server API request, open a request input and set Value comes from to Signed-in user's ID, email, name, phone, or custom attribute. Hourzero fills in the value itself; the model never sees or changes it, so it can't look up another customer by mistake.

For example, Acme's Credit balance action calls GET https://api.acme.com/users/{user_id}/credits with its API key in a header, and fills user_id from Signed-in user's ID. A signed-in customer asks "How many credits do I have?" and gets their own balance.

When a required value isn't known, for example because the visitor isn't signed in, the agent isn't offered the action at all.

Send the token to your API

To let your API check the user itself, turn on Send the signed-in user's token in the action's Headers. Requests then carry the token in a Hourzero-User-Token header, which your API verifies with the same secret:

import { jwtVerify } from "jose";

const secret = new TextEncoder().encode(process.env.HOURZERO_IDENTITY_SECRET);

export async function signedInUser(request) {
  const token = request.headers.get("hourzero-user-token");
  const { payload } = await jwtVerify(token, secret, { algorithms: ["HS256"] });
  return payload.user_id;
}

The header isn't sent for visitors who aren't signed in, and it's dropped if the request redirects to another website.

Website handlers

Your website JavaScript handlers receive the signed-in user as their second argument, as in Chatbase: user_id, user_hash (an HMAC-SHA256 of user_id with your secret, in hex), and user_metadata with email, name, phonenumber, and custom_attributes, next to anon_user_id.

Test it in the Playground

  1. In the Playground, open a website channel and select the visitor button next to Preview. It says Anonymous.
  2. Turn on Signed in and fill in a User ID, and any email, name, phone, or custom attributes.
  3. Chat in the preview. The agent treats you as that user, and actions that use the signed-in user run for them.

If the agent has a secret, Hourzero signs a 10-minute token for the test user, so actions that send the token work too. The test user is saved in your browser only.

Roll or turn off the secret

  • Roll secret creates a new secret. Choose how long the current one keeps working: immediately, 1 hour, 12 hours, or 24 hours. Deploy the new secret to your server before the old one expires, and signed-in visitors won't notice.
  • Turn off deletes the secret. Chats that still send a token are refused until you remove the identify call or generate a new secret. Contacts and their conversations stay.

Moving from Chatbase

The integration is the same. Rename window.chatbase to window.hourzero and window.chatbaseUserConfig to window.hourzeroUserConfig, and sign tokens with your Hourzero secret. The user_id, sub, email, name, phonenumber, and custom_attributes claims work unchanged, and identify and resetUser behave the same way.

Not supported: the older user_hash sign-in method, and details passed next to the token instead of inside it. The stripe_accounts claim is ignored.

Troubleshooting

Visitors are still anonymous

  • Is the script on the page? Chat Iframe installed as Iframe only can't receive a token. Use Script.
  • Does the page call identify? The browser's developer console shows "Hourzero: identify needs { token }" when the call has no valid token.
  • Is identity verification on? Without a secret, chats that send a token are refused.

The chat says the sign-in ended

The token expired or wasn't accepted. The console shows "Hourzero: the identity token expired" or "Hourzero: the identity token was rejected". Check that your server signs with this agent's current secret using HS256, then sign a new token and call identify again.

An action doesn't show up for signed-in visitors

An input that comes from the signed-in user is required and its value is missing. Check that the token includes the email, name, phone, or custom attribute the action needs, and that the attribute key is spelled the same.

Next steps