> For the complete documentation index, see [llms.txt](https://docs.usepylon.com/pylon-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.usepylon.com/pylon-docs/chat-widget/identity-verification.md).

# Identity Verification

{% hint style="info" %}
Please note that if Identity Verification is not enabled, users won't be able to see old chats.
{% endhint %}

This step is optional but **strongly recommended** before you're ready for live production chats. Identity Verification ensures bad actors can't impersonate your customers to see their issues and conversations.

This is done by adding a verification for the identity of the user sending a message through the chat widget, to prevent your customers from manually changing their email in the frontend to impersonate each other.

Pylon is not unique on this front - because a user's identity in the chat is determined client-side, any chat is susceptible to users spoofing their email.

#### HMAC Email Hash

1. **Generate an Identity Secret**<br>

   Starting from your [Chat Widgets page](https://app.usepylon.com/settings/in-app-chat) ([EU](https://app.eu.usepylon.com/settings/chat-widget)), navigating to your Chat Widget's Settings tab.\
   \
   In the "Identity Verification Secret" section, select the "HMAC email hash" option and then click "Generate Secret" if a secret hasn't been generated yet.\
   \
   This will be the only time you will see this key. Save the key somewhere safe, such as a password manager. If you lose your key, you’ll need to regenerate it and replace the key later.\
   \
   Save the changes to your chat widget.
2. **Setup Backend**<br>

   In your backend, hash the user’s email address using HMAC-SHA256 with the secret you just generated. Note that the secret is a hex string and must be decoded to text before use.<br>

   Here are some code snippets to help:

{% tabs %}
{% tab title="JavaScript (Node.js)" %}

```javascript
const { createHmac } = require("node:crypto");

const secret = "GENERATED_IDENTITY_SECRET";
const email = "CHAT_USER_EMAIL";

const secretBytes = Buffer.from(secret, "hex");
const verificationHash = createHmac("sha256", secretBytes)
  .update(email)
  .digest("hex");
```

{% endtab %}

{% tab title="Python" %}

```python
import hmac
import hashlib

def sign_message_with_hmac(message, secret):
    secret_bytes = bytes.fromhex(secret)
    signature = hmac.new(secret_bytes, message.encode(), hashlib.sha256).hexdigest()
    return signature
```

{% endtab %}

{% tab title="Go" %}

```go
package auth

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
)

func SignMessageWithHMAC(message, secret string) (*string, error) {
	secretBytes, err := hex.DecodeString(secret)
	if err != nil {
		return nil, errors.New("unable to decode secret")
	}

	h := hmac.New(sha256.New, secretBytes)
	h.Write([]byte(message))
	signature := h.Sum(nil)

	signedMsg := hex.EncodeToString(signature)

	return &signedMsg, nil
}
```

{% endtab %}
{% endtabs %}

3. **Send this hash to the Frontend and set it on the window object:**<br>

   ```js
   window.pylon.chat_settings.email_hash = HMAC_HASH
   ```

#### JSON Web Token (JWT)

1. **Generate an Identity Secret**<br>

   Starting from your [Chat Widgets page](https://app.usepylon.com/settings/in-app-chat) ([EU](https://app.eu.usepylon.com/settings/chat-widget)), navigating to your Chat Widget's Settings tab.\
   \
   In the "Identity Verification Secret" section, select the "JWT (signed token)" option and then click "Generate Secret" if a secret hasn't been generated yet.\
   \
   This will be the only time you will see this key. Save the key somewhere safe, such as a password manager. If you lose your key, you’ll need to regenerate it and replace the key later.\
   \
   Save the changes to your chat widget.
2. **Setup Backend**<br>

   Generate a short-lived JWT for each authenticated user from your backend. Never expose the shared secret or generate JWTs in browser code.<br>

   Sign the JWT using **HS256** and the generated secret exactly as displayed. Unlike the HMAC secret, the JWT secret must **not** be hex-decoded.<br>

   Include these claims:

   * `email`: The authenticated user’s email address.
   * `aud`: Your Chat Widget App ID.
   * `iat`: The token’s issuance time.
   * `exp`: The expiration time. Tokens may last at most 15 minutes; we recommend 10 minutes.

   Optional identity claims include `name`, `account_id`, `account_external_id`, `contact_id`, and `contact_external_id`. Any identity values supplied to the widget must match those signed into the JWT.<br>

   Return the JWT to your frontend and pass it to the chat widget using the `jwt` field in the next step.\
   \
   Here are some code snippets to help:

{% tabs %}
{% tab title="JavaScript (Node.js)" %}

```javascript
const jwt = require("jsonwebtoken");

function createChatWidgetJWT(secret, appID, email, name = "") {
  const now = Math.floor(Date.now() / 1000);

  return jwt.sign(
    {
      email,
      name,
      aud: appID,
      iat: now,
      exp: now + 10 * 60,
    },
    secret,
    { algorithm: "HS256" },
  );
}
```

{% endtab %}

{% tab title="Python" %}

```python
from datetime import datetime, timedelta, timezone

import jwt


def create_chat_widget_jwt(secret, app_id, email, name=""):
    now = datetime.now(timezone.utc)

    return jwt.encode(
        {
            "email": email,
            "name": name,
            "aud": app_id,
            "iat": now,
            "exp": now + timedelta(minutes=10),
        },
        secret,
        algorithm="HS256",
    )
```

{% endtab %}

{% tab title="Go" %}

```go
package auth

import (
	"time"

	"github.com/golang-jwt/jwt/v5"
)

type ChatWidgetClaims struct {
	Email string `json:"email"`
	Name  string `json:"name"`
	jwt.RegisteredClaims
}

func CreateChatWidgetJWT(secret, appID, email, name string) (string, error) {
	now := time.Now().UTC()

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, ChatWidgetClaims{
		Email: email,
		Name:  name,
		RegisteredClaims: jwt.RegisteredClaims{
			Audience:  jwt.ClaimStrings{appID},
			IssuedAt:  jwt.NewNumericDate(now),
			ExpiresAt: jwt.NewNumericDate(now.Add(10 * time.Minute)),
		},
	})

	return token.SignedString([]byte(secret))
}
```

{% endtab %}
{% endtabs %}

3. **Send the JWT to the Frontend and set it on the window object:**<br>

   ```js
   window.pylon.chat_settings.jwt = YOUR_JWT
   ```

   \
   Generate a fresh JWT whenever the chat widget is initialized or the authenticated user changes. If you were using an email hash before with HMAC, then omit `email_hash` when using JWT authentication.<br>

\ <br>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.usepylon.com/pylon-docs/chat-widget/identity-verification.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
