Connect your authentication system to cStar so customers can use their existing account for support conversations.

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

  1. Your server signs the customer's identity with HMAC-SHA256 using your cStar secret key.
  2. Your frontend passes the signature to the cStar widget.
  3. cStar verifies the signature and creates or links the customer record.
  4. The customer sees their full conversation history.
Your Server  ──(signature)──>  Your Frontend  ──(signature)──>  cStar Widget
                                                                    |
                                                            Verify signature
                                                                    |
                                                           Authenticated chat

Setup

1. Enable Identity Verification

  1. Go to Settings → Team → Chat Widget.
  2. Find the Customer Identity Verification sub-section and toggle it on.
  3. Copy your Production Key (sk_live_...) and Test Key (sk_test_...).
  4. 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:

  1. Go to Settings → Team → Chat Widget → Customer Identity Verification.
  2. Click Test Your Integration.
  3. Paste your customer JSON and signature.
  4. The tool tells you signatureValid: true or signatureValid: 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, and timestamp are signed. Additional fields like avatarUrl and metadata can be passed but aren't part of the signature.