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
Customer logs into your website
Your backend generates a JWT token containing user information, signed with your private key
Your frontend receives the JWT token (via API, rendered in HTML, or stored in a hidden element)
When the Zipchat widget is ready, it dispatches a
zipchat:ready-to-identifyeventYour frontend calls
Zipchat.identify(jwt)with the tokenZipchat verifies the JWT signature using your public key and associates the user with the conversation
The success is cached in localStorage to prevent duplicate requests for 1 hour
Activation
Step 1: Enable the Feature
Navigate to your chat's Integrations page
Find the "Customer identification" section
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
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.
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:
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)
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 successfullySuccess 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 stringWidget 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_tokenValue:
{ 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
endBackend 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
"Widget is not ready"
Ensure the Zipchat widget script is loaded before calling
Zipchat.identify()Wait for the
zipchat:ready-to-identifyevent or checkZipchat.isReadyToIdentify()
"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
"JWT token has expired"
Increase the
exp(expiration) timestamp in your JWT payloadEnsure your server's clock is synchronized
"Email is required in JWT payload"
Always include the
emailfield in your JWT payloadEnsure the email value is not null or empty
"Customer identification is not enabled"
Activate the feature from the Integrations page
Verify the chat has an active customer identification setup
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
Never expose your private key in client-side code, HTML, or JavaScript
Store private keys securely using environment variables or secure key management systems
Use HTTPS for all API endpoints that generate JWT tokens
Set appropriate expiration times (recommended: 1 hour) for JWT tokens
Validate user authentication before generating JWT tokens
Rotate keys if you suspect they've been compromised (contact support to regenerate)
Last updated