Connect your existing authentication system to cStar so customers can use their own account for support. No separate cStar login required.
What you get:
- Customers see their full conversation history across devices
- Agents see verified customer profiles with metadata from your system
- No password fatigue. Your auth is the only auth.
How It Works
- Your server signs the customer's identity with HMAC-SHA256 using your cStar secret key.
- Your frontend passes the signature to the cStar widget.
- cStar verifies the signature and creates or links the customer record.
- The customer sees their full conversation history.
Your Server ──(signature)──> Your Frontend ──(signature)──> cStar Widget
|
Verify signature
|
Authenticated chat
Setup
1. Enable Identity Verification
- Go to Settings → Team → Chat Widget.
- Find the Customer Identity Verification sub-section and toggle it on.
- Copy your Production Key (
sk_live_...) and Test Key (sk_test_...). - Store the production key on your server. Never expose it to the frontend.
2. Create a Backend Endpoint
Your server needs an endpoint that generates a signed identity for the currently logged-in user. The signature covers these fields (sorted alphabetically, compact JSON, no whitespace):
email(required)externalId(required, your system's user ID, as a string)name(optional)timestamp(required, current Unix timestamp in seconds)
Node.js
const crypto = require('crypto');
app.get('/api/cstar-identity', (req, res) => {
const user = req.session.user;
if (!user) return res.status(401).json({ error: 'Not logged in' });
const signedFields = {
email: user.email,
externalId: String(user.id),
name: user.name,
timestamp: Math.floor(Date.now() / 1000)
};
// Remove undefined values, sort keys alphabetically
const filtered = Object.fromEntries(
Object.entries(signedFields)
.filter(([_, v]) => v !== undefined)
.sort(([a], [b]) => a.localeCompare(b))
);
// Sign the compact JSON (no spaces)
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
import hmac, hashlib, json, time
@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())
}
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
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
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,
]);
}
3. Configure the Widget
async function initCStarWidget() {
if (currentUser) {
const { customer, signature } = await fetch('/api/cstar-identity')
.then(r => r.json());
window.cStarConfig = {
teamSlug: 'your-team-slug',
customer,
signature
};
} else {
window.cStarConfig = {
teamSlug: 'your-team-slug',
loginRedirectUrl: '/login?redirect=' + encodeURIComponent(window.location.pathname)
};
}
}
initCStarWidget();
// When user logs out
function handleLogout() {
cStar.logout();
}
Test Mode
During development, use your test key and enable test mode for easier debugging:
window.cStarConfig = {
teamSlug: 'your-team-slug',
testMode: true,
customer,
signature
};
Differences in test mode:
- Timestamp validation is relaxed: 1 hour instead of 5 minutes
- Detailed error messages appear in the console (including the exact payload string to sign)
Remove testMode: true before going to production.
Key Rotation
You can rotate keys any time from Settings → Team → Chat Widget → Customer Identity Verification.
When you rotate:
- 24-hour grace period: both the old key and the new key are accepted.
- Zero-downtime deploys: update your servers within the grace window.
- cStar tries the new key first, then falls back to the old one.
When to rotate:
- Suspected key compromise
- Team member with access to the key leaves
- Regular security hygiene (recommended: quarterly)
Signature Testing Tool
Verify your integration without deploying:
- Go to Settings → Team → Chat Widget → Customer Identity Verification.
- Click Test Your Integration.
- Paste your customer JSON and signature.
- The tool tells you
signatureValid: trueorsignatureValid: false. It does not reveal the expected signature.
Error Codes
| Code | Cause | Fix |
|---|---|---|
INVALID_SIGNATURE |
HMAC doesn't match | Check your secret key and JSON serialization (compact, sorted keys) |
SIGNATURE_EXPIRED |
Timestamp is more than 5 minutes old (production) | Generate fresh signatures on each page load, not cached |
MISSING_REQUIRED_FIELD |
externalId, email, or timestamp missing |
Include all required fields in the signed payload |
IDENTITY_NOT_ENABLED |
Feature not enabled for this team | Toggle it on in Settings |
RATE_LIMITED |
Too many failed verification attempts | Wait and retry. Persistent failures mean a bug in your signing code. |
Troubleshooting
"Signature is invalid"
- Sort object keys alphabetically before signing.
- Use compact JSON with no extra whitespace:
JSON.stringify(obj)in JS,json.dumps(obj, separators=(',', ':'))in Python. - Verify you're using the correct key (test vs. production).
- In test mode, the error message includes the exact string to sign.
"Signature expired"
- Generate signatures server-side on each page load. Never cache them.
- Make sure your server clock is synced (NTP).
"Customer not linked"
- External IDs are case-sensitive.
- The first verified login creates the link. Subsequent logins update name/email if they've changed.
- If a customer with the same email already exists (no external ID yet), cStar links them automatically.
Security Notes
- Never expose your secret key to the frontend.
- Timestamps provide replay protection (5-minute window in production).
- Rate limiting guards against brute-force signature guessing.
- All communication must use HTTPS.
- Only
email,externalId,name, andtimestampare signed. Additional fields likeavatarUrlandmetadatacan be passed but aren't part of the signature.