เอกสารประกอบ

การยืนยันตัวตน

จดจำบุคคลที่ล็อกอินแล้วและกู้คืนประวัติบทสนทนาของพวกเขาข้ามอุปกรณ์ โดยใช้ลายเซ็นที่ backend ของคุณควบคุม

ทำไมต้องยืนยัน#

โดยค่าเริ่มต้น SDK เป็นนิรนาม: มันสร้าง visitor_id ที่เสถียรและเก็บไว้ใน keystore ของแพลตฟอร์ม นั่นเพียงพอสำหรับหนึ่งบทสนทนาบนหนึ่งอุปกรณ์ เพื่อจดจำผู้ใช้ที่เฉพาะเจาะจง กู้คืนประวัติเมื่อล็อกอินใหม่หรือบนอุปกรณ์อื่น และผูกบทสนทนาเข้ากับโปรไฟล์ contact ได้อย่างเชื่อถือได้ backend จำเป็นต้องมีหลักฐานว่า client เป็นผู้ที่อ้างว่าเป็นจริง ถ้า SDK เพียงส่งว่า "ฉันคือ user 42" ใครก็ตามก็สามารถปลอมเป็น id อื่นและอ่านแชทของคนอื่นได้ — ดังนั้น Respondo จึงกำหนดให้ต้องมีลายเซ็นเชิงเข้ารหัส userHash ที่มีเพียง backend ของคุณเท่านั้นที่สร้างได้

การได้มาซึ่ง identity secret#

identity_secret คือสตริงลับที่ผูกกับ เอเจนต์ ของคุณ (AI worker ที่ช่องวิดเจ็ตของคุณเชื่อมต่ออยู่) สร้างมันในแดชบอร์ดที่ Channels → Widget → Identity verification เนื่องจากมันอยู่บนเอเจนต์ ทุกช่องทางที่ใช้เอเจนต์นั้น — ทั้งวิดเจ็ตเว็บและ SDK มือถือ — จึงใช้ secret เดียวกันร่วมกัน มันให้สิทธิ์ในการเซ็นตัวตน จึงต้องอยู่บน backend ของคุณเท่านั้น และต้องไม่ถูกส่งไปในแอปเด็ดขาด

สูตร userHash#

userHash คือ HMAC-SHA256 บนสตริงตัวตนที่ถูกเซ็นเพียงสตริงเดียว เข้ารหัสเป็น hex ตัวพิมพ์เล็ก:

สูตรtext
userHash = HMAC_SHA256( identity_secret, payload )

payload = userId          // ถ้ามีการตั้ง userId
        = email           // มิฉะนั้น ถ้ามีการตั้ง email
        = (invalid)       // ถ้าทั้งคู่ว่าง ก็ไม่มีอะไรให้เซ็น
  • secret คือ key ของ HMAC; สตริงตัวตนคือ message — ไม่ใช่สลับกัน
  • เซ็นเพียงสตริงเดียวเท่านั้น — คือ userId ดิบ (หรืออีเมล) โดยไม่มี salt หรือ JSON wrapper
  • หากคุณส่งทั้ง userId และอีเมล ให้เซ็น userId (มันมีความสำคัญเหนือกว่า)
  • ผลลัพธ์เป็น hex (64 อักขระสำหรับ SHA-256) ไม่ใช่ base64

ตัวอย่างสำหรับ backend#

คำนวณลายเซ็นบน backend ของคุณ แล้วส่ง userHash ที่เสร็จแล้วให้แอป

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

// HMAC_SHA256(identity_secret, userId) เป็น 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");

// คืน userHash เพื่อส่งเข้า Respondo.identify บน client มือถือ
function computeUserHash(identitySecret, userId) {
  return crypto
    .createHmac("sha256", identitySecret)
    .update(userId, "utf8")
    .digest("hex");
}
Pythonpython
import hmac
import hashlib

# คืน userHash เพื่อส่งเข้า Respondo.identify บน 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
// คืน userHash เพื่อส่งเข้า Respondo.identify บน client มือถือ
function computeUserHash(string $identitySecret, string $userId): string {
    return hash_hmac('sha256', $userId, $identitySecret);
}

แฮชเดินทางไปถึง Respondo ได้อย่างไร#

Respondo ไม่มี endpoint สำหรับตัวตนโดยเฉพาะ ไม่มีเส้นทาง /identity ให้เรียก และไม่มีอะไรต้องลงทะเบียน userHash เป็น ฟิลด์ ที่ติดไปกับคำขอที่ SDK ส่งอยู่แล้ว

มีสองช่วง และมีเพียงช่วงแรกเท่านั้นที่คุณต้องสร้างเอง:

  1. backend ของคุณ → แอปของคุณ คุณส่งแฮชด้วยวิธีใดก็ได้ตามต้องการ วิธีที่ประหยัดที่สุดคือเพิ่มฟิลด์พิเศษในผลลัพธ์ของการล็อกอิน/บูตสแตรปที่คุณคืนอยู่แล้ว — ไม่ต้องมีรอบคำขอเพิ่ม endpoint เฉพาะบน backend ของคุณเอง (เช่น POST /myapp/identity ที่คืน { userId, userHash }) ก็ใช้ได้ดีเช่นกัน Respondo ไม่ได้โฮสต์ endpoint นั้น — คุณเป็นผู้เขียนมันเอง
  2. แอปของคุณ → Respondo ส่วนนี้จัดการให้คุณแล้ว เมื่อคุณเรียก identify SDK จะแนบแฮชไปกับทุกคำขอที่เกี่ยวข้อง และ backend จะตรวจสอบซ้ำทุกครั้ง
SDK ใส่แฮชไว้ตรงไหน (สำหรับอ้างอิง — คุณไม่ต้องส่งเองด้วยมือ)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 (subscribe/identify JSON frames after connect — not in the handshake URL)
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

แฮชถูกตรวจสอบซ้ำในทุกคำขอ ไม่ใช่แลกเปลี่ยนเพียงครั้งเดียวต่อเซสชัน — นั่นคือสิ่งที่ทำให้ userId ที่ถูกขโมยไปนั้นไร้ประโยชน์โดยลำพัง

หาก endpoint สำหรับส่งแฮชบน backend ของคุณ ยังไม่ได้สร้าง คำขอของแอปคุณเองจะได้ 404, identify จะไม่ถูกเรียก และแชทจะทำงานแบบนิรนาม นั่นเป็นพฤติกรรมที่คาดไว้ ไม่ใช่ข้อผิดพลาดของ Respondo — 404 บนเส้นทางของคุณเองคืองานที่ค้างอยู่ฝั่งคุณ ไม่ใช่การเชื่อมต่อที่พัง

การส่งตัวตนไปยัง SDK#

ดึงตัวตนที่ถูกเซ็นจาก backend ของคุณ แล้วส่ง userHash เข้าไปใน identify บนแพลตฟอร์มใดก็ได้:

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

พฤติกรรมเมื่อ userHash ไม่ถูกต้อง#

เมื่อเปิดการยืนยันบนเอเจนต์ และลายเซ็นหายไปหรือผิด backend จะทำการ ลดระดับลงเป็นนิรนามอย่างเงียบ ๆ: แชทยังทำงานได้, visitor_id ถูกเก็บไว้ และบทสนทนาถูกผูกเข้ากับ contact นิรนาม — แต่ไม่มีลิงก์ไปยังโปรไฟล์และไม่มีประวัติข้ามอุปกรณ์ หากผู้ใช้ "ไม่ได้รับการจดจำ" แทบทุกครั้งมักเป็นเรื่องลายเซ็น: ตรวจสอบว่าคุณเซ็น userId (ไม่ใช่อีเมลหรือ JSON), ใช้ identity_secret ที่ถูกต้อง และสร้าง hex ตัวพิมพ์เล็ก หาก identity_secret ของเอเจนต์ว่างเปล่า การยืนยันจะปิดอยู่ และ userId / อีเมล จะถูกยอมรับตามที่เป็น

การหมุน identity_secret มีผลเป็นการตัดสลับแบบเด็ดขาด: ทุก hash ที่สร้างด้วย secret เก่าจะหยุดยืนยันในทันที ดังนั้นผู้ใช้ที่ล็อกอินอยู่แล้วจะถอยกลับเป็นนิรนามอย่างเงียบ ๆ จนกว่า backend ของคุณจะคำนวณและส่ง userHash ของพวกเขาใหม่ด้วย secret ใหม่ ควรหมุน secret เฉพาะเมื่อคุณสามารถอัปเดตฝั่งที่ทำการเซ็นได้ในช่วงเวลาเดียวกันเท่านั้น