Tài liệu

Xác minh danh tính

Nhận diện một người đã đăng nhập và khôi phục lịch sử trò chuyện của họ trên nhiều thiết bị, sử dụng một chữ ký mà backend của bạn kiểm soát.

Vì sao cần xác minh#

Theo mặc định SDK là ẩn danh: nó tạo một visitor_id ổn định và giữ nó trong keystore của nền tảng. Điều đó là đủ cho một cuộc hội thoại trên một thiết bị. Để nhận diện một người dùng cụ thể, khôi phục lịch sử khi đăng nhập lại hoặc trên một thiết bị khác, và liên kết đáng tin cậy một cuộc hội thoại với một hồ sơ liên hệ, backend cần bằng chứng rằng client đúng là người mà nó tuyên bố. Nếu SDK chỉ đơn giản gửi "Tôi là người dùng 42", bất kỳ ai cũng có thể giả mạo một id khác và đọc cuộc trò chuyện của người khác — nên Respondo yêu cầu một chữ ký mật mã, userHash, mà chỉ backend của bạn mới có thể tạo ra.

Lấy identity secret#

identity_secret là một chuỗi bí mật gắn với agent của bạn (AI agent mà kênh widget của bạn được kết nối tới). Hãy tạo nó trong dashboard tại Kênh → Widget → Xác minh danh tính. Vì secret nằm trên agent, mọi kênh dùng agent đó — cả widget web lẫn mobile SDK — đều dùng chung một secret. Nó cấp quyền ký danh tính, nên nó chỉ được sống trên backend của bạn và không bao giờ được đóng gói trong ứng dụng.

Công thức userHash#

userHash là một HMAC-SHA256 trên một chuỗi danh tính được ký duy nhất, mã hóa dưới dạng hex chữ thường:

Công thứctext
userHash = HMAC_SHA256( identity_secret, payload )

payload = userId          // nếu userId được đặt
        = email           // ngược lại, nếu email được đặt
        = (không hợp lệ)  // nếu cả hai đều trống, không có gì để ký
  • Secret là khóa HMAC; chuỗi danh tính là thông điệp — không phải ngược lại.
  • Ký đúng một chuỗi — giá trị userId thô (hoặc email), không salt hay bọc JSON.
  • Nếu bạn truyền cả userId và email, hãy ký userId (nó được ưu tiên).
  • Đầu ra là hex (64 ký tự cho SHA-256), không phải base64.

Ví dụ backend#

Tính chữ ký trên backend của bạn và trao userHash đã hoàn tất cho ứng dụng.

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

// HMAC_SHA256(identity_secret, userId) dưới dạng hex chữ thường.
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");

// Trả về userHash để truyền vào Respondo.identify trên client di động.
function computeUserHash(identitySecret, userId) {
  return crypto
    .createHmac("sha256", identitySecret)
    .update(userId, "utf8")
    .digest("hex");
}
Pythonpython
import hmac
import hashlib

# Trả về userHash để truyền vào Respondo.identify trên client di động.
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
// Trả về userHash để truyền vào Respondo.identify trên client di động.
function computeUserHash(string $identitySecret, string $userId): string {
    return hash_hmac('sha256', $userId, $identitySecret);
}

Hash đến Respondo bằng cách nào#

Respondo không có endpoint identity nào cả. Không có route /identity để gọi và cũng không có gì để đăng ký. userHash là một trường đi kèm những request mà SDK vốn đã gửi.

Hai chặng, và chỉ chặng đầu là do bạn xây:

  1. Backend của bạn → ứng dụng của bạn. Bạn chuyển hash theo cách nào tùy ý. Rẻ nhất là thêm một trường vào response đăng nhập/bootstrap mà bạn vốn đã trả về — không tốn thêm round trip. Một endpoint riêng trên backend của chính bạn (ví dụ POST /myapp/identity trả về { userId, userHash }) cũng tốt tương đương. Respondo không host endpoint đó — bạn tự triển khai.
  2. Ứng dụng của bạn → Respondo. SDK lo phần này. Một khi bạn gọi identify, SDK tự đính hash vào mọi request liên quan và backend xác minh lại nó mỗi lần.
SDK đặt hash ở đâu (tham khảo — bạn không tự gửi những cái này)text
POST /api/v1/chat                      body   identity.userHash
GET  /api/v1/chat/resume               query  user_hash
GET  /api/v1/chat/history              query  user_hash
GET  /api/v1/chat/ws                   frames    user_hash (các frame JSON subscribe/identify sau khi kết nối — không nằm trong URL handshake)
GET  /api/v1/widget/tours              query  user_hash
GET  /api/v1/widget/checklists         query  user_hash
POST /api/v1/widget/push/register      body   user_hash

Hash được kiểm tra lại ở mọi request, chứ không phải đổi một lần lấy phiên — đó chính là điều khiến một userId bị đánh cắp trở nên vô dụng nếu đứng một mình.

Nếu endpoint phân phối trên backend của bạn chưa được xây, request của chính ứng dụng bạn sẽ trả 404, identify không bao giờ được gọi, và cuộc hội thoại chạy ẩn danh. Đó là hành vi dự kiến, không phải lỗi của Respondo — 404 trên đường dẫn của chính bạn là việc cần làm ở phía bạn, không phải tích hợp bị hỏng.

Truyền danh tính cho SDK#

Lấy danh tính đã ký từ backend của bạn, rồi truyền userHash vào identify trên bất kỳ nền tảng nào:

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,
));

Hành vi khi userHash không hợp lệ#

Khi việc xác minh được bật trên agent và chữ ký bị thiếu hoặc sai, backend thực hiện một hạ cấp âm thầm về ẩn danh: cuộc hội thoại vẫn hoạt động, visitor_id được giữ lại, và cuộc hội thoại được liên kết với liên hệ ẩn danh — nhưng không có liên kết đến hồ sơ và không có lịch sử đa thiết bị. Nếu một người dùng "không được nhận diện", hầu như luôn là do chữ ký: hãy kiểm tra rằng bạn đã ký userId (không phải email hay JSON), dùng đúng identity_secret, và phát ra hex chữ thường. Nếu identity_secret của agent trống, việc xác minh tắt và userId / email được chấp nhận nguyên trạng.

Xoay vòng identity_secret thực chất là một cú chuyển đổi dứt khoát: mọi hash được tạo bằng secret cũ ngừng xác minh ngay lập tức, nên những người dùng đã đăng nhập âm thầm rơi về ẩn danh cho đến khi backend của bạn tính lại và cung cấp lại userHash của họ với secret mới. Chỉ xoay secret khi bạn có thể cập nhật phía ký trong cùng một khoảng thời gian.