> ## Documentation Index
> Fetch the complete documentation index at: https://docs.connectly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Sign in your users

> Tell the widget who your logged-in users are, so each one keeps a single conversation on every device and shows up in your inbox by name 🔐

<Accordion title="Connectly Webchat is in development">
  This widget is being rolled out. Interfaces on this page may change before general
  availability. The engineering documentation site, which tracks the current build, is at
  [webchat.connectly.ai/docs](https://webchat.connectly.ai/docs/).
</Accordion>

If your users log in on your site, you can tell the widget who they are. A signed-in user
keeps one conversation on every browser and device, shows up in your inbox under their name
and email, and the AI agent uses what you tell it about them instead of asking for it again.

It takes three pieces:

1. An **identity key**, created once. Connectly keeps only its public half; the private key
   stays on your server.
2. A short-lived **identity token** your server signs for the logged-in user.
3. A function on the page that fetches a fresh token from your server whenever the widget
   asks for one.

An anonymous visitor needs none of this. A page that never passes a token gets the anonymous
widget described everywhere else in these docs.

## Create an identity key

As an owner of the business, open **Settings → Webchat → Sign-in** in Connectly and select
**Create key**. Connectly shows two values:

| value | what it is |
| - | - |
| Key id | `wik_…`. It is public, and goes in each token's `kid` header. |
| Private key | A PEM. It is shown this once and Connectly does not keep it: store it in your server's secret store. Anyone holding it can sign in as any of your users. |

A business has one live key at a time. To replace it, revoke it on the same tab, create a new
one, and deploy the new key to your servers. Users cannot sign in between the revoke and the
moment your servers sign with the new key. Like a client key, a new identity key takes up to a
minute to start verifying, and a revoked one stops verifying within a minute.

## Sign a token on your server

The token is a JWT signed with **RS256** and your private key, with the key id in the `kid`
header.

| claim | required | rules |
| - | - | - |
| `sub` | yes | Your own id for the user: a string, or a non-negative integer, read as its digits. The same `sub` is the same conversation everywhere. At most 124 characters of letters, digits, `.`, `_`, `-` and `@`. |
| `iat`, `exp` | yes | The token may live at most 5 minutes: it is spent on one session start, not stored. |
| `jti` | yes | A fresh id for every token. |
| `name` | no | Shown in your inbox, and the AI agent addresses the user by it. At most 128 characters. |
| `email` | no | A valid email address. Shown in your inbox and to the AI agent. |
| `attributes` | no | Up to 10 string values about the user for the AI agent, such as `{ "plan": "pro" }`. Names are lowercase letters, digits and `_`, start with a letter, and are at most 40 characters; values are at most 200 characters; an empty or null value is ignored. |

Together, `name`, `email` and `attributes` must fit in 4 KB once encoded as JSON. A token
that breaks any rule is refused, and the chat does not start. It never falls back to an
anonymous visitor.

In Node, with `jsonwebtoken`:

```js theme={null}
import { randomUUID } from 'node:crypto';
import jwt from 'jsonwebtoken';

// PRIVATE_KEY and KEY_ID: the private key and key id shown when you created the key
// `user` is whoever is logged in on this request
const token = jwt.sign(
  { sub: String(user.id), name: user.name, email: user.email, attributes: { plan: user.plan } },
  PRIVATE_KEY,
  { algorithm: 'RS256', keyid: KEY_ID, expiresIn: '5m', jwtid: randomUUID() },
);
```

In Python, with PyJWT:

```python theme={null}
import time
import uuid

import jwt

# PRIVATE_KEY and KEY_ID: the private key and key id shown when you created the key
# `user` is whoever is logged in on this request
now = int(time.time())
token = jwt.encode(
    {
        "sub": str(user.id),
        "name": user.name,
        "email": user.email,
        "attributes": {"plan": user.plan},
        "iat": now,
        "exp": now + 300,
        "jti": str(uuid.uuid4()),
    },
    PRIVATE_KEY,
    algorithm="RS256",
    headers={"kid": KEY_ID},
)
```

Serve it from an endpoint only a logged-in user can reach, and return the token as plain text.
The token passes through the user's browser, where anyone can decode it, so put nothing in it
the user should not see.

## Hand it to the widget

Give the widget a function that fetches a token. It calls the function each time it starts a
session, so the function must fetch a new token every time rather than reuse one.

```js theme={null}
// TOKEN_URL: the endpoint above, which returns a token for the logged-in user
const getToken = async () => {
  const res = await fetch(TOKEN_URL);
  if (!res.ok) throw new Error(`webchat token: ${res.status}`);
  return res.text();
};

// script tag, from a `defer` or `type="module"` script
window.ConnectlyWebchat.init({ clientKey: '<your-client-key>', identityTokenProvider: getToken });

// or on the element itself
document.querySelector('connectly-webchat').identityTokenProvider = getToken;
```

```tsx theme={null}
// React
<ConnectlyWebchat clientKey="<your-client-key>" identityTokenProvider={getToken} />
```

Set it before the visitor opens the chat. Passing a new function for the same user changes
nothing; passing one where there was none is a sign-in, and the widget starts over as that
user. The function has 15 seconds to answer, and an error it throws stops the chat from
starting rather than signing the user in anonymously.

When you revoke the key a user signed in with, their session ends within the hour, and the
widget asks your function for a new token.

## Signing out

When your page logs the user out, tell the widget, so the next person on that browser does
not see their conversation:

```js theme={null}
window.ConnectlyWebchat.logout();
// or
document.querySelector('connectly-webchat').logout();
```

In React, stop passing `identityTokenProvider`.

A signed-in conversation is never written to the browser's storage: it lives in the page,
and the widget signs the user in again on the next page load.

## What happens to the details

* **Your inbox** shows the token's name and adds its email to the contact. If someone on
  your team renames the contact, the rename sticks; later tokens do not overwrite it.
* **The AI agent** sees the name, email and attributes your latest token gave, never an edit
  made in the inbox. A token without a name keeps the name an earlier token gave.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.