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

# Chat identity verification

> Sign chat variables on your backend so they pass the Trusted only check on actions and lookups

Sign the variables your chat sends, such as the customer's email, so Typewise
can trust them. This works the same for the chat widget and the Chat SDK.

## Overview

Chat variables come from the browser, so a visitor can change them. That's fine
for preferences like locale or theme, but not for identity: a spoofed email
would let anyone act as another customer.

With identity verification, your backend signs a JWT with a Typewise secret. The
chat sends the token, Typewise checks the signature, and the claims inside
become verified values.

### When you need it

You need it if any chat action or lookup uses the **Trusted only** check on an
input mapped to a context variable. See
[Context Variables](https://docs.typewise.app/documentation/settings/context-variables.md).

Without a valid signature, the check fails. The AI Agent then asks the customer
to confirm (for `user_email`) or hands off to a Human Agent.

Skip it for values that don't need to be trusted, such as UI locale, theme, or
feature flags.

## Set it up

**1. Copy your signing secret**

Open [Identity verification](https://platform.typewise.app/channels/chat/installation/identity-verification)
under chat installation. Copy the `sk_...` key and store it in your
backend's secrets manager.

Regenerating the key invalidates the previous one.

**2. Sign a token on your backend**

Sign a JWT with HS256. Put the variables you want verified in the payload,
and keep the expiry short (7 days or less).

```javascript Node.js theme={null}
import jwt from "jsonwebtoken";

const token = jwt.sign(
  {
    user_email: "john.doe@example.com",
    subscription_tier: "premium",
  },
  process.env.TYPEWISE_SDK_SECRET,
  { expiresIn: "7d" },
);
```

```python Python theme={null}
import jwt, os
from datetime import datetime, timedelta, timezone

token = jwt.encode(
    {
        "user_email": "john.doe@example.com",
        "subscription_tier": "premium",
        "exp": datetime.now(timezone.utc) + timedelta(days=7),
    },
    os.environ["TYPEWISE_SDK_SECRET"],
    algorithm="HS256",
)
```

```php PHP theme={null}
use Firebase\JWT\JWT;

$token = JWT::encode(
  [
    'user_email' => 'john.doe@example.com',
    'subscription_tier' => 'premium',
    'exp' => time() + 60 * 60 * 24 * 7,
  ],
  $_ENV['TYPEWISE_SDK_SECRET'],
  'HS256',
);
```

Return the token only to the signed-in user's page.

**3. Pass the token to the chat**

Pass the token as `signedVariables`. Values in `variables` stay unverified.

**Chat widget**

```javascript theme={null}
window.twConfig = {
  clientId: "YOUR_CLIENT_ID",
  signedVariables: "<JWT from your backend>",
  variables: { locale: "en-US" },
};
```

If the visitor signs in after the page loads, pass a new token with
[`window.twSdk.setVariables`](https://docs.typewise.app/guides/channels/chat-widget/javascript-api#verify-the-visitor-after-login).

**Chat SDK**

Pass a function. The SDK calls it each time it authenticates, so tokens
can stay short-lived:

```javascript theme={null}
const chat = createClient({
  clientId: "YOUR_CLIENT_ID",
  signedVariables: () =>
    fetch("/api/typewise-token").then((response) => response.text()),
});
```

See [Identify signed-in users](https://docs.typewise.app/guides/channels/chat-sdk/page-context.md#identify-signed-in-users).

> [!WARNING]
>
> Never send the signing secret to the browser or commit it to source control.
> Anyone who holds it can forge any variable for any customer.

## Verification outcomes

| Signature | Signed claims | Pass the Trusted only check |
| :- | :- | :- |
| Valid JWT | Verified | Yes |
| Invalid, missing, or expired | Discarded | No |

An invalid, missing, or expired token doesn't reject the session. Typewise
discards the token's claims, and only values you pass separately in `variables`
remain, as unverified. If an action reaches a **Trusted only** input mapped to
one of them, the
[runtime rules](https://docs.typewise.app/documentation/settings/context-variables.md#when-a-trusted-only-input-is-not-satisfied)
apply.

## Related

* [Context Variables](https://docs.typewise.app/documentation/settings/context-variables.md): declare
  variables and configure action and lookup inputs
* [Security & verification](https://docs.typewise.app/documentation/actions/security.md): the trust model
  behind input customization
* [Chat configuration](https://docs.typewise.app/documentation/behavior/chat.md): chat channel overview
* [Embed the chat widget in Shopify](https://docs.typewise.app/guides/channels/chat-widget/shopify.md):
  sign variables in Liquid without a backend


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