Overview
Customer Identity Verification allows you to connect your existing authentication system to cStar. When enabled, your customers can use their existing account to access support—no separate cStar login required.
Benefits
- Single Sign-On Experience: Customers use their existing account
- Cross-Device History: Conversations follow customers across devices
- Richer Context: Pass customer metadata (plan, account age, etc.) to agents
- Verified Profiles: Know exactly who you're talking to
How It Works
- Your server generates an HMAC-SHA256 signature of the customer's identity
- Your frontend passes this signature to the cStar widget
- cStar verifies the signature and creates/links the customer record
- Customer sees their full conversation history
Your Server ──(signature)──> Your Frontend ──(signature)──> cStar Widget
│
▼
Verification
│
▼
✓ Authenticated Chat
Setup Guide
Step 1: Enable in Settings
- Go to Settings → Widget → Customer Identity Verification
- Toggle Enable Identity Verification
- Copy your Production Key (
sk_live_...) and Test Key (sk_test_...) - Store the production key securely on your server (never expose to frontend!)
Step 2: Create Backend Endpoint
Create an API endpoint that generates signed customer data:
Node.js / Express:
const crypto = require('crypto');
app.get('/api/cstar-identity', (req, res) => {
// Must be authenticated in your system
const user = req.session.user;
if (!user) {
return res.status(401).json({ error: 'Not logged in' });
}
// Only these fields are signed (flat, no nested objects)
const signedFields = {
email: user.email,
externalId: String(user.id),
name: user.name,
timestamp: Math.floor(Date.now() / 1000)
};
// Remove undefined, sort keys alphabetically
const filtered = Object.fromEntries(
Object.entries(signedFields)
.filter(([_, v]) => v !== undefined)
.sort(([a], [b]) => a.localeCompare(b))
);
// Generate HMAC signature
const payload = JSON.stringify(filtered);
const signature = crypto
.createHmac('sha256', process.env.CSTAR_IDENTITY_SECRET)
.update(payload)
.digest('hex');
res.json({ customer: filtered, signature });
});
Python / Flask:
import hmac, hashlib, json, time
from flask import jsonify, session
@app.route('/api/cstar-identity')
def cstar_identity():
user = session.get('user')
if not user:
return jsonify({'error': 'Not logged in'}), 401
signed_fields = {
'email': user['email'],
'externalId': str(user['id']),
'name': user.get('name'),
'timestamp': int(time.time())
}
# Remove None values, sort keys
signed_fields = {k: v for k, v in sorted(signed_fields.items()) if v is not None}
payload = json.dumps(signed_fields, separators=(',', ':'))
signature = hmac.new(
CSTAR_SECRET.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return jsonify({'customer': signed_fields, 'signature': signature})
Ruby / Rails:
def identity
return render json: { error: 'Not logged in' }, status: 401 unless current_user
signed_fields = {
email: current_user.email,
externalId: current_user.id.to_s,
name: current_user.name,
timestamp: Time.now.to_i
}.compact.sort.to_h
payload = signed_fields.to_json
signature = OpenSSL::HMAC.hexdigest(
'SHA256',
ENV['CSTAR_IDENTITY_SECRET'],
payload
)
render json: { customer: signed_fields, signature: signature }
end
PHP / Laravel:
public function cstarIdentity(Request $request)
{
$user = auth()->user();
if (!$user) {
return response()->json(['error' => 'Not logged in'], 401);
}
$signedFields = array_filter([
'email' => $user->email,
'externalId' => (string) $user->id,
'name' => $user->name,
'timestamp' => time(),
], fn($v) => $v !== null);
ksort($signedFields);
$payload = json_encode($signedFields, JSON_UNESCAPED_SLASHES);
$signature = hash_hmac('sha256', $payload, env('CSTAR_IDENTITY_SECRET'));
return response()->json([
'customer' => $signedFields,
'signature' => $signature,
]);
}
Step 3: Update Frontend Widget
async function initCStarWidget() {
const isLoggedIn = !!currentUser;
if (isLoggedIn) {
// Fetch signed identity from your backend
const { customer, signature } = await fetch('/api/cstar-identity')
.then(r => r.json());
window.cStarConfig = {
teamSlug: 'your-team-slug',
customer,
signature
});
} else {
// Unauthenticated - show login prompt
window.cStarConfig = {
teamSlug: 'your-team-slug',
loginRedirectUrl: '/login?redirect=' + encodeURIComponent(window.location.pathname)
});
}
}
// Call on page load
initCStarWidget();
// Call cStar.logout() when user logs out
function handleLogout() {
cStar.logout();
// ... rest of your logout logic
}
Test Mode
Use test mode during development for easier debugging:
- Use your Test Key (
sk_test_...) on your backend - Add
testMode: trueto widget initialization - Timestamp validation is relaxed (1 hour instead of 5 minutes)
- Detailed error messages appear in console
window.cStarConfig = {
teamSlug: 'your-team-slug',
testMode: true, // Relaxed validation for development
customer,
signature
});
Remove testMode: true before going to production!
Key Rotation
Keys can be rotated at any time from Settings. When you rotate keys:
- 24-hour grace period: Both old and new keys work for 24 hours
- Zero-downtime updates: Update your servers without breaking existing sessions
- Automatic fallback: cStar tries the new key first, then falls back to the previous key
When to rotate:
- Suspected key compromise
- Team member with access leaves
- Regular security rotation (recommended: quarterly)
Signature Testing Tool
Use the built-in testing tool to verify your integration:
- Go to Settings → Widget → Customer Identity Verification
- Click Test Your Integration
- Paste your customer JSON and signature
- See instant feedback on whether verification would succeed
Error Codes
Error CodeCauseSolutionINVALID_SIGNATUREHMAC doesn't matchCheck your secret key and JSON serializationSIGNATURE_EXPIREDTimestamp > 5 min oldGenerate fresh signatures on each page loadMISSING_REQUIRED_FIELDNo externalId or emailInclude all required fieldsRATE_LIMITEDToo many failed attemptsWait and retry, check for bugs
Troubleshooting
"Signature is invalid"
- Ensure you're sorting object keys alphabetically before signing
- Check that you're using
JSON.stringify()with no extra whitespace - Verify you're using the correct key (test vs. production)
"Signature expired"
- Generate signatures server-side on each page load, not cached
- Ensure your server clock is synchronized (NTP)
- In test mode, timestamps are valid for 1 hour
"Customer not linked"
- External IDs are case-sensitive
- The first verified login creates the link—subsequent logins update it
Security Notes
- Never expose your secret key to the frontend
- Signatures include timestamps to prevent replay attacks
- Rate limiting protects against brute-force signature guessing
- Use HTTPS for all API calls