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

Sign user data using the private key with the RS256 algorithm. Here's an example in 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)

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

Option B: API Endpoint

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

Option C: Hidden Meta Tag

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

Step 4: Call Zipchat.identify

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

<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)

<!-- 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)

<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)

# 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)

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)

Last updated