Docs

Identity verification

Recognize a signed-in person and restore their conversation history across devices, using a signature your backend controls.

Why verify#

By default the SDK is anonymous: it generates a stable visitor_id and keeps it in the platform keystore. That is enough for one conversation on one device. To recognize a specific user, restore history on relogin or another device, and reliably bind a conversation to a contact profile, the backend needs proof the client is who it claims to be. If the SDK simply sent "I am user 42", anyone could spoof another id and read someone else's chat — so Respondo requires a cryptographic signature, userHash, that only your backend can produce.

Getting the identity secret#

The identity_secret is a secret string bound to your agent (the AI worker your widget channel is connected to). Find it in the agent settings in the dashboard. It grants the right to sign identities, so it must live only on your backend and must never be shipped in the app.

The userHash formula#

userHash is an HMAC-SHA256 over a single signed identity string, encoded as lowercase hex:

Formulatext
userHash = HMAC_SHA256( identity_secret, payload )

payload = userId          // if userId is set
        = email           // otherwise, if email is set
        = (invalid)       // if both are empty, there is nothing to sign
  • The secret is the HMAC key; the identity string is the message — not the other way round.
  • Sign exactly one string — the raw userId (or email), with no salt or JSON wrapper.
  • If you pass both userId and email, sign the userId (it takes priority).
  • Output is hex (64 characters for SHA-256), not base64.

Backend examples#

Compute the signature on your backend and hand the finished userHash to the app.

Gogo
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
)

// HMAC_SHA256(identity_secret, userId) as lowercase hex.
func ComputeUserHash(identitySecret, userID string) string {
    mac := hmac.New(sha256.New, []byte(identitySecret))
    mac.Write([]byte(userID))
    return hex.EncodeToString(mac.Sum(nil))
}
Node.jsjavascript
const crypto = require("crypto");

// Returns the userHash to pass into Respondo.identify on the mobile client.
function computeUserHash(identitySecret, userId) {
  return crypto
    .createHmac("sha256", identitySecret)
    .update(userId, "utf8")
    .digest("hex");
}
Pythonpython
import hmac
import hashlib

# Returns the userHash to pass into Respondo.identify on the mobile client.
def compute_user_hash(identity_secret: str, user_id: str) -> str:
    return hmac.new(
        identity_secret.encode(),
        user_id.encode(),
        hashlib.sha256,
    ).hexdigest()
PHPphp
<?php
// Returns the userHash to pass into Respondo.identify on the mobile client.
function computeUserHash(string $identitySecret, string $userId): string {
    return hash_hmac('sha256', $userId, $identitySecret);
}

Passing identity to the SDK#

Fetch the signed identity from your backend, then pass the userHash into identify on any platform:

Kotlinkotlin
Respondo.identify(
    RespondoIdentity(userId = "42", email = "user@example.com", userHash = hash),
)
Swiftswift
Respondo.identify(
    RespondoIdentity(userId: "42", email: "user@example.com", userHash: hash)
)
Dartdart
Respondo.identify(RespondoIdentity(
  userId: '42', email: 'user@example.com', userHash: hash,
));

Invalid userHash behavior#

When verification is enabled on the agent and the signature is missing or wrong, the backend performs a silent downgrade to anonymous: the chat still works, the visitor_id is kept, and the conversation is bound to the anonymous contact — but there is no link to the profile and no cross-device history. If a user "is not recognized", it is almost always the signature: check that you signed the userId (not email or JSON), used the right identity_secret, and emitted lowercase hex. If the agent's identity_secret is empty, verification is off and userId / email are accepted as-is.

Rotating the identity_secret is effectively a hard cutover: every hash produced with the old secret stops verifying at once, so already-signed-in users silently fall back to anonymous until your backend recomputes and re-supplies their userHash with the new secret. Roll the secret only when you can update the signing side in the same window.