# Zipchat Customer Identification Integration (Non-Shopify stores)

### Customer Identification - JWT Authentication

#### Overview

The Customer Identification feature allows Zipchat to identify authenticated users on your website. This enables the AI assistant to:

* Know more about the user (name, email, additional attributes)
* Provide user-specific information (e.g., order tracking) without requiring email collection
* Automatically associate leads with conversations
* Skip lead collection forms for verified users

**Note**: This feature is only available for non-Shopify integrations. Shopify stores have their own built-in user identification mechanism.

#### How It Works

1. Customer logs into your website
2. Your backend generates a JWT token containing user information, signed with your private key
3. Your frontend receives the JWT token (via API, rendered in HTML, or stored in a hidden element)
4. When the Zipchat widget is ready, it dispatches a `zipchat:ready-to-identify` event
5. Your frontend calls `Zipchat.identify(jwt)` with the token
6. Zipchat verifies the JWT signature using your public key and associates the user with the conversation
7. The success is cached in localStorage to prevent duplicate requests for 1 hour

#### Activation

**Step 1: Enable the Feature**

1. Navigate to your chat's **Integrations** page
2. Find the "Customer identification" section
3. Click the **Activate** button to enable customer identification

Once activated, you'll receive:

* A **Private Key** (RSA) - Use this to sign JWT tokens on your backend
* A **Public Key** (RSA) - Zipchat uses this to verify your tokens (you can use it for testing)

**Important**: Keep your private key secure and never expose it in client-side code. Only use it on your backend server.

**Step 2: Generate JWT Token on Your Backend**

{% hint style="danger" %}
**You must generate your JWT token in the backend ( not in the FrontEnd ).**\
If you generate the JWT token in the frontend, you will allow anyone to read your Private Key, and hackers will be able to authenticate with Zipchat as ANY USER.
{% endhint %}

Sign user data using the private key with the RS256 algorithm. Here's an example in Ruby:

```ruby
require 'jwt'
require 'openssl'

private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY_FROM_UI)

payload = {
  email: user.email,                    # Required
  first_name: user.first_name,          # Optional
  last_name: user.last_name,            # Optional
  additional_attributes: {              # Optional (JSON object)
    user_id: user.id,
    subscription_tier: user.tier,
    # ... any other custom attributes
  },
  iat: Time.now.to_i,                   # Issued at (Unix timestamp)
  exp: 1.hour.from_now.to_i             # Expiration (Unix timestamp)
}

jwt = JWT.encode(payload, private_key, 'RS256')
```

**JWT Payload Keys We Read:**

| Field                   | Type   | Required | Description                                 |
| ----------------------- | ------ | -------- | ------------------------------------------- |
| `email`                 | String | Yes      | User's email address                        |
| `first_name`            | String | No       | User's first name                           |
| `last_name`             | String | No       | User's last name                            |
| `additional_attributes` | Object | No       | Custom JSON object with any additional data |
| `iat`                   | Number | Yes      | Issued at time (Unix timestamp)             |
| `exp`                   | Number | Yes      | Expiration time (Unix timestamp)            |

**Step 3: Send JWT to Frontend**

Choose the method that best fits your stack:

**Option A: Render in HTML (Server-Side Rendering)**

```erb
<script>
  window.zipchatJWT = '<%= jwt_token %>';
</script>
```

**Option B: API Endpoint**

```javascript
// Fetch from your API
fetch('/api/zipchat-token')
  .then(response => response.json())
  .then(data => data.token);
```

**Option C: Hidden Meta Tag**

```html
<meta name="zipchat-jwt" content="YOUR_JWT_TOKEN">
```

**Step 4: Call Zipchat.identify**

Add this script to your HTML where users are logged in:

```javascript
<script>
  function identifyCustomer() {
    getZipchatToken().then(token => {
      if (window.Zipchat && window.Zipchat.identify) {
        Zipchat.identify(token);
      }
    });
  }

  // Wait for Zipchat to be ready before identifying
  window.addEventListener('zipchat:ready-to-identify', identifyCustomer);

  // If Zipchat is already ready, identify immediately
  if (window.Zipchat && window.Zipchat.isReadyToIdentify && window.Zipchat.isReadyToIdentify()) {
    identifyCustomer();
  }

  // Implement this function to get the JWT token from your backend
  function getZipchatToken() {
    // Example: Fetch from API
    return fetch('/your-backend/generate-zipchat-token', {
      method: 'POST',
      credentials: 'include'
    })
    .then(response => response.json())
    .then(data => data.token);
    
    // Alternative examples:
    // return Promise.resolve(window.zipchatJWT); // If rendered by backend
    // return Promise.resolve(document.querySelector('meta[name="zipchat-jwt"]').content); // If in hidden tag
  }
</script>
```

#### Function Parameters

**Zipchat.identify(jwt)**

| Parameter | Type   | Required | Description                 |
| --------- | ------ | -------- | --------------------------- |
| `jwt`     | String | Yes      | JWT token signed with RS256 |

#### Server Responses

The identification function will log different messages to the browser console based on the server response:

**Success Response**

* **HTTP Status**: 200
* **Response**: `{ "success": true }`
* **Console Log**: `Customer identified successfully`
* Success is cached in localStorage for 1 hour

**Error Response**

* **HTTP Status**: 422 Unprocessable Entity
* **Response**: `{ "success": false }`
* **Console Log**: `Error identifying customer: [error details]`

The server returns `{ "success": false }` in the following cases:

* JWT token is missing or invalid
* Customer identification is not enabled for the chat
* JWT signature verification failed
* JWT token has expired
* Email is missing from JWT payload
* Chat not found

#### Client-Side Validation

The JavaScript function performs validation before sending data to the server:

**Invalid JWT Token**

If JWT is not a non-empty string:

```
Zipchat.identify: JWT must be a non-empty string
```

**Widget Not Ready**

If Zipchat widget is not ready to identify:

```
Zipchat.identify: Not ready to identify. Wait for "zipchat:ready-to-identify" event or check Zipchat.isReadyToIdentify()
```

**Network Errors**

If the request fails due to network issues:

```
Error identifying customer: [error details]
```

#### Rate Limiting

To prevent duplicate requests, successful identifications are cached in localStorage:

* **Key**: `zipchat_identified_visitor_token`
* **Value**: `{ value: visitor_token, expiration: timestamp + 1 hour }`
* **Duration**: 1 hour

The cache expires when:

* 1 hour passes
* User clears localStorage
* User starts a new conversation (new visitor\_token)

#### Implementation Examples

> **⚠️ Important**: The examples below are provided as reference guides to help you understand the overall implementation approach. They are meant to give you direction on how to integrate customer identification, but you should adapt them to fit your specific stack, framework, and security requirements.

**Basic Implementation (Server-Side Rendered JWT)**

```html
<!-- In your layout/template where user is logged in -->
<script>
  window.zipchatJWT = '<%= @jwt_token %>';
</script>

<script>
  function identifyCustomer() {
    if (window.Zipchat && window.Zipchat.identify && window.zipchatJWT) {
      Zipchat.identify(window.zipchatJWT);
    }
  }

  window.addEventListener('zipchat:ready-to-identify', identifyCustomer);
  
  if (window.Zipchat && window.Zipchat.isReadyToIdentify && window.Zipchat.isReadyToIdentify()) {
    identifyCustomer();
  }
</script>
```

**Complete Implementation (API-Based)**

```javascript
<script>
  function identifyCustomer() {
    // Fetch JWT from your backend API
    fetch('/api/zipchat-token', {
      method: 'POST',
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json',
      }
    })
    .then(response => response.json())
    .then(data => {
      if (data.token && window.Zipchat && window.Zipchat.identify) {
        Zipchat.identify(data.token);
      }
    })
    .catch(error => {
      console.error('[Zipchat] Error fetching JWT token:', error);
    });
  }

  // Wait for Zipchat to be ready
  window.addEventListener('zipchat:ready-to-identify', identifyCustomer);

  // Check if already ready
  if (window.Zipchat && window.Zipchat.isReadyToIdentify && window.Zipchat.isReadyToIdentify()) {
    identifyCustomer();
  }
</script>
```

**Backend Example (Ruby on Rails)**

```ruby
# app/controllers/api/zipchat_tokens_controller.rb
class Api::ZipchatTokensController < ApplicationController
  before_action :authenticate_user!

  def create
    private_key = OpenSSL::PKey::RSA.new(Rails.application.credentials.zipchat_private_key)
    
    payload = {
      email: current_user.email,
      first_name: current_user.first_name,
      last_name: current_user.last_name,
      additional_attributes: {
        user_id: current_user.id,
        subscription_tier: current_user.subscription_tier,
        account_created_at: current_user.created_at.iso8601
      },
      iat: Time.now.to_i,
      exp: 1.hour.from_now.to_i
    }
    
    jwt = JWT.encode(payload, private_key, 'RS256')
    
    render json: { token: jwt }
  end
end
```

**Backend Example (Node.js/Express)**

```javascript
const jwt = require('jsonwebtoken');
const fs = require('fs');

// Load private key
const privateKey = fs.readFileSync('path/to/private_key.pem', 'utf8');

app.post('/api/zipchat-token', authenticateUser, (req, res) => {
  const payload = {
    email: req.user.email,
    first_name: req.user.firstName,
    last_name: req.user.lastName,
    additional_attributes: {
      userId: req.user.id,
      subscriptionTier: req.user.subscriptionTier
    },
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour
  };

  const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
  
  res.json({ token });
});
```

#### Troubleshooting

**Common Issues**

1. **"Widget is not ready"**
   * Ensure the Zipchat widget script is loaded before calling `Zipchat.identify()`
   * Wait for the `zipchat:ready-to-identify` event or check `Zipchat.isReadyToIdentify()`
2. **"JWT signature verification failed"**
   * Verify you're using the correct private key from the Integrations page
   * Ensure you're using RS256 algorithm
   * Check that the public key matches the private key pair
3. **"JWT token has expired"**
   * Increase the `exp` (expiration) timestamp in your JWT payload
   * Ensure your server's clock is synchronized
4. **"Email is required in JWT payload"**
   * Always include the `email` field in your JWT payload
   * Ensure the email value is not null or empty
5. **"Customer identification is not enabled"**
   * Activate the feature from the Integrations page
   * Verify the chat has an active customer identification setup
6. **Identification not working**
   * Check browser console for error messages
   * Verify the JWT token is valid using a JWT decoder
   * Ensure the visitor\_token exists in localStorage
   * Check that the identification request is not being blocked by rate limiting (wait 1 hour or clear localStorage)

#### Security Best Practices

1. **Never expose your private key** in client-side code, HTML, or JavaScript
2. **Store private keys securely** using environment variables or secure key management systems
3. **Use HTTPS** for all API endpoints that generate JWT tokens
4. **Set appropriate expiration times** (recommended: 1 hour) for JWT tokens
5. **Validate user authentication** before generating JWT tokens
6. **Rotate keys** if you suspect they've been compromised (contact support to regenerate)


---

# Agent Instructions: 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:

```
GET https://docs.zipchat.ai/customer-identification.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
