# Identify visitors

> Pass signed-in users to the chat with a hash computed on your server, so conversations link to the right customer and can't be spoofed.

For signed-in users, your app fills in the same details the [pre-chat form](/docs/widget/customize#ask-for-visitor-details-before-the-chat) would ask for (email, name, phone) and adds the user's ID. The details show on the conversation straight away, and the form only asks for anything you leave out.

The user ID is what keeps all of one person's chats under a single contact, across visits and devices. Because anyone can change what a browser sends, your server signs the ID with a secret only you and Giov know. Without a valid signature, a user ID is kept as an unverified claim and never groups chats into a contact.

## How it works

1. Generate an identity secret in the dashboard.
2. Your server computes `userHash`, the HMAC-SHA256 of the user ID keyed with that secret.
3. Your page sets `window.GiovChatSettings` with the user's details, ID and hash before the widget loader.
4. Optionally, require a verified identity for every conversation that sends a user ID.

## 1. Generate the identity secret

1. Open **Settings → Chat widget → Visitor details**.
2. Turn on **Signed-in users**, then select **Generate secret**.
3. Copy the secret straight away. You won't see it again. Store it in your server's secret manager, for example as `GIOV_IDENTITY_SECRET`.

> [!WARNING]
> Keep the secret on your server. Never ship it to the browser, a mobile app or a public repository. Anyone holding it can impersonate your users.

## 2. Compute the hash on your server

`userHash` is the HMAC-SHA256 of the user ID as lowercase hex. Use the secret exactly as issued, as UTF-8 text; don't base64-decode it.

```js title="server.js" tab="Node.js"
import { createHmac } from "node:crypto";

// Server only. Read the secret from your server configuration.
const userHash = createHmac("sha256", process.env.GIOV_IDENTITY_SECRET)
  .update(String(user.id))
  .digest("hex");
```

```python title="server.py" tab="Python"
import hashlib
import hmac
import os

# Server only. Read the secret from your server configuration.
user_hash = hmac.new(
    os.environ["GIOV_IDENTITY_SECRET"].encode("utf-8"),
    str(user.id).encode("utf-8"),
    hashlib.sha256,
).hexdigest()
```

```php title="server.php" tab="PHP"
<?php
// Server only. Read the secret from your server configuration.
$userHash = hash_hmac('sha256', (string) $user->id, getenv('GIOV_IDENTITY_SECRET'));
```

## 3. Pass the ID and hash to the widget

On signed-in pages, render the user's ID and hash from your server, just before the standard install snippet:

```html title="index.html"
<script>
  window.GiovChatSettings = {
    userId: "USER_ID",     // the same id your server hashed
    userHash: "USER_HASH"  // computed on your server, never in the browser
  };
</script>
<script src="https://widget.giovchat.app/YOUR_WORKSPACE_ID" async></script>
```

You can pass the user's contact details in the same object:

```html title="index.html"
<script>
  window.GiovChatSettings = {
    userId: "USER_ID",
    userHash: "USER_HASH",
    email: "ada@example.com",
    phone: "+44 20 7946 0000",
    firstName: "Ada",
    lastName: "Lovelace"
  };
</script>
<script src="https://widget.giovchat.app/YOUR_WORKSPACE_ID" async></script>
```

| Field | Notes |
|---|---|
| `userId` | The exact string you hashed, at most 200 characters. Convert numeric IDs to strings, as the server examples do. |
| `userHash` | Lowercase hex, computed on your server. |
| `email`, `phone`, `firstName`, `lastName` | Optional. Shown to your team with the conversation. |

Only string values are read; anything else is ignored. Passing any of these fields also skips the [pre-chat form](/docs/widget/customize#ask-for-visitor-details-before-the-chat).

> [!NOTE]
> The widget reads `window.GiovChatSettings` once, when the loader runs. Set it before the loader script. In a single-page app, set it before you add the loader to the page.

> [!NOTE]
> Identity is attached when a conversation starts. The browser keeps a signed-in user's conversation for that user only: when someone else signs in on the same device, or the user signs out, the widget starts a new conversation instead of reopening theirs. A conversation started before signing in carries on after it, as the same user's.

## Sign-in and sign-out without a page reload

In a single-page app, tell the widget when the user changes:

```js
// After sign-in, with the userHash from your server
window.ChatWidget.identify({ userId: "user_123", userHash: "<hash>" });

// On sign-out
window.ChatWidget.identify(null);
await window.ChatWidget.resetSession();
```

`identify` with another user, or `null`, starts a new support conversation and drops the details you passed for the previous user. `resetSession()` forgets the conversation in this browser, so the next person starts fresh.

## 4. Only accept signed IDs (optional)

In **Settings → Chat widget → Visitor details**, turn on **Only accept signed IDs** to reject new conversations that send a user ID without a valid hash. You need a secret first.

Anonymous visitors, who send no user ID, can still start conversations.

## What Giov does with each case

| Your page sends | Result |
|---|---|
| `userId` with a valid `userHash` | The conversation belongs to an identified customer and is linked to their contact. |
| `userId` without a hash, or with a wrong one | Stored as an unverified claim and never linked to an existing contact. Rejected for new conversations when **Only accept signed IDs** is on. |
| Contact details only | Stored as the visitor's details. |
| Nothing | An anonymous visitor. |

## Rotate the secret

Select **Rotate secret** under **Signed-in users** in **Settings → Chat widget → Visitor details**. The current secret stops working immediately, so hashes signed with it no longer verify until your server uses the new one. If **Only accept signed IDs** is on, signed-in visitors can't start conversations during that gap. Update your server configuration right after rotating.

Turning **Signed-in users** off deletes the secret: no user ID verifies until you turn it on and generate a new one. GiovCopilot sign-in with user hashes stops too.

## Content Security Policy

The inline `<script>` that sets `window.GiovChatSettings` must be allowed by your page's `script-src`, with a nonce or a hash. If your site forbids inline scripts, set the same object from an external script file that loads before the loader. See [Content Security Policy](/docs/widget/csp).

## Mobile apps and Copilot

- The mobile SDKs take the same `userId` and `userHash`. See [Mobile apps](/docs/mobile).
- Giov Copilot can use the same identity to recognise signed-in users. See [Giov Copilot](/docs/copilot).
