For the complete documentation index, see llms.txt. This page is also available as Markdown.

Identity Verification (Secure User Login)

Identity verification proves that the authUser passed to the K-Link chat widget really is the person your website says it is. Without it, anyone who opens the widget could claim any email address and read that user's conversation history.

It works the same way as Intercom's identity verification: your server computes an HMAC-SHA256 hash of the user's identity using a secret only you and K-Link know, and passes it to the widget as userHash. The chat backend recomputes the hash and only honors the identity when it matches. The secret never appears in the browser, so visitors cannot forge a hash for someone else's identity.

Choosing the identity value

The identity is whatever stable, unique value identifies the user in your system — a customer id, email, phone number, or any other unique id. Pass it in the identity field. If you don't set identity, the widget uses email as the identity, so existing email-based integrations keep working unchanged.

The one rule that matters: the userHash must sign the exact value that serves as the identity — i.e. identity if you send it, otherwise email. That same value is what the backend uses to recognize a returning user, so pick one identity strategy per channel and stay with it. email/phone you also pass are stored as profile attributes for display; only the identity value is signed and deduplicated on.

1. Generate the channel's identity secret

Use the K-Link public API with your existing API token (kpt_…):

curl -X POST "https://apigw.klinkcx.com/api/v1/webchat/channels/<channelId>/identity-verification/secret" \
  -H "Authorization: Bearer kpt_xxxxxxxx"
# => { "requestId": "…", "secret": "9f2c4a…64 hex chars…" }

<channelId> is your klink.cloud webchat channel id — the same value passed to the widget as scId. The API only accepts channels that belong to your tenant.

The plaintext secret is returned only by this call. Store it in your server's secret manager — never in frontend code, HTML, or public repos. Calling the endpoint again rotates the secret (the old one stops working immediately).

2. Compute userHash on your server

Hash the exact identity string you pass to the widget — the value of identity (or email if you don't send identity). The comparison is byte-for-byte, so watch out for casing/whitespace differences. In the examples below identity is the customer id you pass as authUser.identity.

Node.js:

const crypto = require("crypto");

const userHash = crypto
  .createHmac("sha256", process.env.KLINK_IDENTITY_SECRET)
  .update(identity) // e.g. user.id — or user.email if that's your identity
  .digest("hex");

PHP:

Python:

3. Pass it to the widget

Render the hash into the page for the logged-in user. Identify by any stable unique value via identity:

If you identify users by email, omit identity and sign the email instead — the existing behavior:

Anonymous visitors (no login) simply omit authUser — nothing changes for them.

4. Enable enforcement

While enforcement is off, identities without a userHash are still accepted (legacy behavior), but a provided hash is always validated — deploy step 3, watch the browser console for [KLinkChat] warnings, and once clean, turn enforcement on:

From then on, any authUser without a valid userHash is rejected: the visitor is treated as anonymous, the server logs the rejection, and the widget prints the reason to the browser console. Check status at any time:

Behavior summary

Channel state

authUser without hash

authUser with valid hash

authUser with invalid hash

No secret configured

trusted (legacy)

trusted (legacy, cannot verify)

trusted (legacy, cannot verify)

Secret set, enforcement off

trusted (legacy)

verified ✓

rejected → anonymous

Secret set, enforcement on

rejected → anonymous

verified ✓

rejected → anonymous

Here "hash" means a userHash that correctly signs the identity value. A hash that signs a different value (e.g. the email when identity is set) is treated as invalid.

Additional guarantees, regardless of enforcement:

  • A user record that was ever verified can only be matched or updated by another verified claim — an unverified claim can never take over a verified user's conversation history or overwrite their profile.

  • Message sender identity is stamped server-side from the registered session; a client cannot send messages as someone else.

  • Switching a channel's identity strategy (e.g. from email to a customer id) keys returning users differently, so the same person may start a fresh record. Pick one identity value per channel and keep it stable.

Notes

  • Rotating a secret invalidates hashes computed with the old one. Deploy the new secret to your server at the same time; sessions registered in between fall back to anonymous rather than breaking the widget.

  • The API endpoints use the same kpt_ API token you already use for the K-Link public API, and only accept webchat channels that belong to your account.