# Identity verification for signed-in users

> Recognize signed-in users in the chat widget: the identity key, computing userHash in Node.js, PHP and Python, verified e-mail and sign-out.

If people sign in to your site or app, the widget can recognize them. They then see their conversations on any device, and your team sees one contact instead of several anonymous ones.

## Why a signature is needed

The browser cannot be trusted: anyone can call `identify` with someone else's e-mail. So your server computes `userHash`, an HMAC-SHA256 signature with the identity key, which the browser never sees.

## The identity key

The key is in the chat widget settings, on the User recognition tab. Only owners can see and change it.

- Keep the key on your server only, for example in the `SUPPORT_IDENTITY_SECRET` variable. Never put it in browser code.
- When you create a new key, the old one keeps working for 24 hours so you can update your server.

![User recognition settings with the key hidden](https://hrc.lt/screens/en/project-identity.desktop.1600.1a0c4890.webp)

## What to sign

The signature covers the string `v1:<id>:<e-mail>`. The e-mail is trimmed and lower case; a missing part stays empty.

```text
v1:<externalId>:<e-mail, trimmed and lower case>

v1:12345:jonas@example.com   id and e-mail
v1:12345:                    id only
v1::jonas@example.com        e-mail only, needs the setting
                             "My app verifies e-mail addresses"
```

`userHash` is the HMAC-SHA256 of this string with the identity key, written as 64 hex characters.

## Code for your server

Node.js:

```js
// Node.js (Express): your own endpoint, for the signed-in user only
import { createHmac } from 'node:crypto';

app.get('/api/support-hash', requireLogin, (req, res) => {
  const user = req.user;
  const email = (user.email ?? '').trim().toLowerCase();
  const canonical = `v1:${user.id ?? ''}:${email}`;
  const userHash = createHmac('sha256', process.env.SUPPORT_IDENTITY_SECRET)
    .update(canonical)
    .digest('hex');
  res.json({ userHash });
});
```

PHP:

```php
<?php
// PHP: mb_strtolower, not strtolower, so Ū and Ž are lowered too
$email = mb_strtolower(trim($user->email ?? ''), 'UTF-8');
$canonical = 'v1:' . ($user->id ?? '') . ':' . $email;
$secret = getenv('SUPPORT_IDENTITY_SECRET');
$userHash = hash_hmac('sha256', $canonical, $secret);
```

Python:

```python
# Python
import hashlib
import hmac
import os

user_id = "" if user.id is None else str(user.id)
email = (user.email or "").strip().lower()
canonical = f"v1:{user_id}:{email}"
secret = os.environ["SUPPORT_IDENTITY_SECRET"].encode()
user_hash = hmac.new(secret, canonical.encode(), hashlib.sha256).hexdigest()
```

## Code for the browser

Always pass `externalId` as a string: a number is ignored.

```js
// In the browser: ask your own server for the hash of the signed-in user
const { userHash } = await fetch('/api/support-hash', {
  credentials: 'include',
}).then((r) => r.json());
HrcWidget('identify', {
  externalId: String(user.id ?? ''),
  email: user.email,
  name: user.name,
  userHash,
});

// On logout
HrcWidget('reset');
```

You can pass profile fields with `identify` too, see [Visitor profile](https://hrc.lt/en/docs/widget/visitor-profile).

## "My app verifies e-mail addresses"

Turn this on only if your app checks that the e-mail belongs to the user (it sends a code or a link). A signed e-mail then joins the same person's e-mails and conversations into one contact across all your chat widgets and mailboxes.

While this setting is off, always pass the user's `externalId`. A signed e-mail without an id is refused with `IDENTITY_INVALID`, and the e-mail stays unverified until the person answers one of our e-mails.

## Signed-in users only

The "Signed-in users only" mode lets only recognized users write. An anonymous session cannot send messages or change the profile (`IDENTITY_REQUIRED`).

## Check a signature

The settings have a "Check a calculated value" box: enter an id, an e-mail and the value your server computed, and the app tells you whether it matches. The check runs on the server; the key is not sent to the browser. If it does not match, check that the e-mail is lower case and that you use this widget's key.

You see failures on your site too: `HrcWidget('on', 'identify-failed', ...)` gets the code, never the signature itself.

## Sign-out

When the user signs out, call `HrcWidget('reset')`. The widget forgets the session, and the next person on the same device does not see someone else's conversations.
