# Identifying users

> Set a custom identifier or secure JWT token to identify visitors in Unless.

This file is the complete, self-contained version of the guide for automated consumption. It carries the
supporting JavaScript API pages the guide refers to, so no further page needs to be fetched. The
human-readable version lives at https://docs.unless.com/getting-started/identifying-users/.

Usually you don't want a visitor to see a certain experiences multiple times, for example a popup that the visitor closes should not be shown again later. To do this, we set a cookie to make sure that doesn't happen. However, if your product runs behind a login, then it's best practice to set a customer identifier before loading our snippet. This will make sure the visitor will always have the same id, across devices and even after clearing cookies.

This guide explains how to identify users with the client-side API. It supports two modes:

* Basic identification with unsecured profile data.
* Secure identification with a backend-signed JWT for trusted traits.

## Set the identifier before the snippet loads (preferred)

If possible, set a stable identifier before loading the Unless snippet. The custom identifier will be used as the identifier of the user. The JWT token is optional and is described below.

```html
<script>
  window.TxtOptions = {
    customIdentifier: identifier, //stable custom identifier
    jwt: token, //optional secure traits JWT
    data: [] //optional profile data
  }
</script>

<!-- enter your Unless snippet code below -->
<script data-unless=...></script>
```

### Alternatively set the identifier after the snippet loads

In cases you can't set the identifier or JWT before the script loads. For example this can happen in single page application (SPA) that load statically but use runtime requests to log the user in. In that case, you can use our `initialize` function on the Txt object instead. When using this approach make sure to first set `autoInitialize` to `false` in the `TxtOptions` object to prevent loading of the user before the initialize call happened (see example below).

Signature:

```js
Txt.initialize({
  identifier: string,
  data?: [], //see signature: https://docs.unless.com/javascript-api/audiences/updating-a-profile/
  jwt?: string,
})
```

Parameters:

* `identifier` (string, required): Stable user ID. This becomes the visitor identifier.
* `data` (object, optional): Unsecured traits stored in `profile`.
* `jwt` (string, optional): Signed JWT containing secure traits.

Use `Txt.initialize(...)` when identifying a user for the first time on page load, and `Txt.setJwt(...)` when updating or rotating the signed JWT later in the same session. This is important to prevent the token from expiring if you have a Single Page Application.

### Example identification

When using the Txt object, you have to make sure the script is fully loaded before calling a function on it. To do this use the txt-loaded event.

```html
<script>
  window.TxtOptions = {
    autoInitialize: false, //this prevents loading before initialize() happened.
  }
</script>

<!-- enter your Unless snippet code below -->
<script data-unless=...></script>

<!-- ... later in your code after you got the logged in user ... -->

<script>
  Txt.initialize({
    identifier: 'visitor_123', // replace with backend provided stable identifier
    data: [
      {key: 'firstName', value: 'John', type: 'text'}, // optionally add traits that are not secure
    ],
    jwt: 'your token' // replace with backend provided token
  })
</script>
```

## Secure identification (recommended)

Secure identification protects any user information in our system using a signed JWT token. This is highly recommended to protect user information and conversational data. Some functionality in the Unless system require verified identification. For example, if you want to send data to 3rd party API's through agentic skills. In that case the data you are going to send needs to be validated and secure. To do this you need to create a JWT token on your backend to provide secure traits. If you create a skill that contains a variable with the same name as one of your secure traits, then we will automatically use the value of that secured trait for the variable.  
You can also put authentication tokens inside a secured traits so we can pass that on to your receiving API, make sure this token is called `authToken` and is **short lived** and not a permanent authentication token. Don't store any information in the JWT that should be a secret to your user (like secret API keys). By calling the property `authToken` we provide some extra checks on our backend to make it isn't send to the client and/or logged.

### What is a JSON web token (JWT)?

A JSON Web Token (JWT) is an industry standard way to sign data. It typically consists of three parts, separated by dots. A typical JWT looks like this: header.payload.signature.

* The header specifies the token type (JWT) and the signing algorithm (e.g., HS256).
* The payload contains claims about the user or session (e.g., user_id, email).
* Finally, the signature ensures that the token hasn't been tampered with, using a secret or private key.

For more information see: [https://www.jwt.io/introduction#what-is-json-web-token](https://www.jwt.io/introduction#what-is-json-web-token).

### JWT requirements

* **Algorithm:** HS256
* **Secret:** your account API key (from the dashboard)
* **Required claim:** `sub` (must match `identifier`)
* **Recommended claim:** `exp` (expiration timestamp, in seconds)
* **Optional claims:** `traits`. **Note**: any **variables** you define in your AI skills are automatically filled using these traits and can be considered secure.

Example payload (all of the traits are optional and customizable):

```json
{
  "sub": "visitor_123",
  "traits": {
    "plan": "enterprise",
    "region": "emea",
    "email": "user@company.com",
    "authToken": "your-custom-token"
  },
  "exp": 1769509999
}
```

:::danger
Avoid setting long lifetimes for access tokens. Recommended is to use a maximum of 1 hour.
:::

### Refreshing the JWT token

If you have a Single Page Application, you have to make sure the JWT token does not expire. If your application refreshes or rotates secure identification tokens during a session, you can update the JWT without reloading the Unless snippet by using the `Txt.setJwt()` function.

```js
window.Txt.setJwt(newJwt)
```

This updates the JWT used for secure identification requests. If the AI component is already loaded, active chat connections will also refresh their authentication automatically.

#### Arguments

| Argument | Type     | Description                                                                                                     |
| -------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| `jwt`    | `string` | A backend-signed JWT using `HS256`. The token should contain a `sub` claim that matches the current identifier. |

#### Example

```js
const refreshedJwt = await fetch('/api/unless/identify-jwt').then((res) => res.text())

window.Txt.setJwt(refreshedJwt)
```

### Handling an expired JWT

Visitors often leave a tab open for longer than the JWT lives. When the AI component reconnects with an expired JWT, the chat stops working and the visitor is asked to refresh the page. To prevent this, define `Txt.onJwtExpired` and return a new JWT from it.

```js
window.Txt.onJwtExpired = async () => {
  return await fetch('/api/unless/identify-jwt').then((res) => res.text())
}
```

The AI component calls this function when the server rejects the current JWT as expired, then retries the connection with the JWT you return. If the function returns nothing, returns the same JWT, or throws an error, the visitor is asked to refresh the page.

Define `Txt.onJwtExpired` in addition to `Txt.setJwt()`, not instead of it. `Txt.setJwt()` keeps the JWT fresh while the visitor uses the page. `Txt.onJwtExpired` is the fallback for when it has expired anyway, for example after the device was asleep.

#### Return value

| Type                                         | Description                                                          |
| -------------------------------------------- | -------------------------------------------------------------------- |
| `Promise<string \| null> \| string \| null` | A new backend-signed JWT, or `null` if the visitor can't be renewed. |

### Overview

1. Your backend creates an HS256 JWT using your account API key. This API key should stay on the backend and should never be accessible by your end-users in your application.
2. Your frontend calls `Txt.initialize({ identifier, data, jwt })`.
3. Unless stores unsecured `data` in `profile` and the secured JWT `traits` in `secureProfile`.
4. Make sure to refresh the JWT token before it expires.

### Backend example (Node.js)

```js
import jwt from "jsonwebtoken"

const apiKey = process.env.UNLESS_API_KEY

const token = jwt.sign(
  {
    sub: "visitor_123",
    traits: {
      plan: "enterprise",
      region: "emea",
      email: "user@company.com",
    },
    exp: Math.floor(Date.now() / 1000) + 300,
  },
  apiKey,
  { algorithm: "HS256" },
)
```

## Troubleshooting

* **Invalid token:** Check the signing secret and algorithm (must be HS256).
* **Missing `sub`:** Tokens must include a `sub` claim that matches `identifier`.
* **Expired token:** Ensure `exp` is in the future and in seconds (not milliseconds).

## Supporting reference

The pages below are the parts of the JavaScript API that the guide above links to.

### Initialization

> How to wait for the Txt object to be available before calling its methods.

In case you need to call a function on the global Txt object, you have to make sure the object is loaded properly. You can do this by using our event `txt-loaded`.

```javascript
document.addEventListener('txt-loaded', (e) => {
  console.log('Txt object is now available.');
  // do something like Txt.updateProfile(...);
});
```

### Updating a profile

> Enrich visitor profiles with key-value data using updateProfile() to power audience targeting.

Audiences are joined by matching user profile fields using the audience builder in the Unless dashboard. To be able to set up these audiences, you first need to send us some profile fields using the updateProfile function:

```javascript
// update the profile with additional data points
Txt.updateProfile([
  {key: 'firstName', value: 'John', type: 'text'},
  {key: 'role', value: 'CMO', type: 'text'}
])
```

The update profile function enriches an existing visitor profile with additional data points.

```javascript
 Txt.updateProfile(data)
```

The data argument is required and should be an array of objects. The data object within the array should be structured as follows:

| Key   | Type                  | Description                                                                                         |
| ----- | --------------------- | --------------------------------------------------------------------------------------------------- |
| key   | string **required**   | The key to store as a trait.                                                                        |
| value | string **required**   | The value to store.                                                                                 |
| type  | string **required**   | The type of the data that you are sending, options are **text**, **number**, **bool**, **date**.    |

:::note
The **Txt.setProfile(_data_)** function works exactly the same as **Txt.updateProfile(_data_)** except it will not immediately force an update. Instead, it will only happen locally and waits until the next call. This enables you to do multiple set profile calls in a function. This is better for resources and network traffic. It works well with the custom startup script feature in Unless.
:::

### Logout

> Fully log out a visitor and clear all Unless storage using Txt.logout().

You can call this function if you want to completely log out the visitor and remove any Unless related local/session storage & cookies.

```javascript
Txt.logout()
```
