Tài liệu

Nhúng vào hệ thống nội bộ

Trong trang này

SotaAgents có thể chạy ngay bên trong trang web của công ty bạn, dưới dạng bong bóng nổi hoặc panel gắn liền trong trang. Người dùng vẫn đăng nhập bằng hệ thống của bạn và không bao giờ thấy màn hình đăng nhập SotaAgents: backend của bạn xác nhận họ là ai, và nền tảng tin vào chữ ký đó chứ không tin bất cứ thứ gì người dùng gõ vào.

Mười lăm giây, không tiếng. Backend của bạn ký request và nhận ticket; frontend gắn panel bằng một lời gọi; trợ lý xuất hiện ngay trong cổng thông tin của chính đối tác — dạng inline, hoặc bong bóng nổi.

Hai cách nhúng#

Chế độNgười truy cập là aiDùng choAi thiết lập
Public Deployment (Configure Embed)Khách ẩn danh, dùng pool Guest Credit riêngWebsite công khai, trang marketing, chat trước bán hàngChủ sở hữu/quản trị viên tổ chức — không cần code
Embed SDK (handshake có chữ ký)Người dùng đích danh, đã là thành viên tổ chứcIntranet, cổng khách hàng, công cụ nội bộChủ sở hữu/quản trị viên tổ chức và đội phát triển của bạn

Trang này nói về cách thứ hai. Cách thứ nhất xem tại Workspace.

Chuẩn bị trước khi bắt đầu#

  • API key và secret tạo tại Console → Tổ chức của bạn → API Keys. Secret chỉ hiện một lần — xem API key.
  • Mọi người sẽ dùng embed đều phải là thành viên đang hoạt động của tổ chức. Hệ thống không tự tạo tài khoản: người lạ bị từ chối chứ không được tạo mới. Hãy mời họ trước, hoặc cấp tài khoản qua SSO.
  • Một backend do bạn kiểm soát. Secret ký request ở phía server và tuyệt đối không được lộ ra trình duyệt.

Luồng handshake#

Sơ đồ tuần tự: giao diện doanh nghiệp xin ticket từ backend doanh nghiệp, backend ký request gửi tới embed API của SotaAgents, ticket trả về và được iframe đổi lấy cookie phiên, và thao tác đăng xuất thu hồi phiên đó
Bắt tay có chữ ký giữa ba bên. API secret không bao giờ rời cột giữa; trình duyệt chỉ giữ một ticket sống 60 giây, rồi tới cookie phiên đã phân vùng.
1

Trang của bạn xin ticket từ backend của bạn

SDK không hề nói chuyện với hệ thống định danh của bạn. Nó gọi hàm getTicket do bạn cung cấp, hàm này gọi endpoint của bạn kèm cookie phiên của bạn.

2

Backend của bạn ký và tạo ticket

Backend đọc danh tính người dùng từ session, ký request bằng API secret, và nhận về một ticket dùng một lần, hiệu lực 60 giây.

3

Iframe đổi ticket lấy phiên

SDK chuyển ticket cho khung SotaAgents; khung này đổi ticket lấy phiên riêng của nó. Ticket không thể dùng lại.

4

SDK tự động xin ticket mới

Gần hết hạn, hoặc sau bất kỳ lỗi 401 nào, SDK tự lặp lại bước 1. Phiên của bạn là vòng đời duy nhất có ý nghĩa — không có gì phải đồng bộ.

Backend của bạn cần làm gì#

Ba việc, và hai việc đầu là chỗ các tích hợp hay sai nhất.

1

Xác định người dùng ở phía server

Dùng đúng thứ cổng thông tin của bạn đang có — SSO, OIDC, hay bảng session riêng. Gửi kèm externalUserId bất biến theo từng người: ánh xạ từ id này sang người dùng SotaAgents chỉ tạo một lần và không bao giờ được cập nhật. Hãy coi đó là ràng buộc của bạn, không phải của chúng tôi — chúng tôi không ép nó. Một id mới cho người mà hệ thống đã biết không bị từ chối: chúng tôi khớp người đó theo email rồi âm thầm tạo thêm một ánh xạ thứ hai, ánh xạ cũ nằm lại ở chỗ mà thu hồi danh tính không chạm tới. Thứ bị từ chối là chiều ngược lại — một id đã tồn tại nhưng đi kèm email khác.

2

Mở một endpoint cấp ticket

Đọc danh tính từ session, tuyệt đối không đọc từ body của request — đọc từ body đồng nghĩa mọi nhân viên đã đăng nhập đều có thể tạo ticket mạo danh đồng nghiệp. Ký lời gọi theo cách mô tả bên dưới rồi trả ticket về cho trình duyệt.

3

Trả về mã trạng thái mà SDK xử lý được

SDK coi 4xx là lỗi vĩnh viễn và dừng; 5xx là lỗi tạm thời và thử lại có giãn cách. Trả 401 khi phiên của bạn đã hết, 409 khi người dùng chưa là thành viên đang hoạt động, và 502 khi không gọi được SotaAgents.

Secret chỉ nằm trên server của bạn

Trình duyệt chỉ nhận ticket ngắn hạn, dùng một lần. API secret lọt xuống front-end đồng nghĩa với việc tạo được ticket cho bất kỳ danh tính nào trong tổ chức. Hãy lưu trong secret manager, không đưa vào source control.

Ký request#

Công thức giống nhau ở mọi ngôn ngữ: dựng chuỗi canonical 5 dòng, HMAC-SHA256 bằng API secret, rồi gửi kết quả dưới dạng v1=<hex chữ thường>. Năm dòng gồm: method, path, timestamp Unix tính bằng giây, nonce, và SHA-256 của body — nối bằng \n, không có newline ở cuối.

Code
POST
/api/embed/auth/tickets
1786800000
3f1c9a7e-5b02-4d6f-9a11-0c8e2d4b7f30
9b2a4c1d8e3f5a70b6c9d2e4f8a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9e1f3a5
Hash đúng chuỗi byte mà bạn gửi đi

Serialize body một lần và dùng lại đúng chuỗi đó cho cả phần hash lẫn phần gửi. Serialize lại lần hai là nguyên nhân phổ biến nhất khiến chữ ký không khớp ở đâu cả — hai bộ mã hoá JSON, hoặc cùng một bộ gọi hai lần, có thể xếp key hay khoảng trắng khác nhau.

JavaScript
import { createHash, createHmac, randomUUID } from 'node:crypto';

const body = JSON.stringify({ externalUserId, email, name }); // serialize ONCE
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = randomUUID();
const bodyHash = createHash('sha256').update(body, 'utf8').digest('hex');

const canonical = ['POST', PATH, timestamp, nonce, bodyHash].join('\n');
const signature = 'v1=' + createHmac('sha256', apiSecret).update(canonical).digest('hex');

Request thực tế trên đường truyền#

Dù ký bằng ngôn ngữ nào, đây là thứ SotaAgents nhận được. Header timestamp và nonce phải mang đúng giá trị đã đưa vào chuỗi canonical; mỗi nonce chỉ được chấp nhận một lần, và request lặp lại bị từ chối trong vòng 5 phút.

HTTP
POST https://app.sotaagents.ai/api/embed/auth/tickets
content-type: application/json
x-sota-key: sota_ek_...
x-sota-timestamp: 1786800000
x-sota-nonce: 3f1c9a7e-5b02-4d6f-9a11-0c8e2d4b7f30
x-sota-signature: v1=4d5e...

{"externalUserId":"e-1042","email":"an.tran@company.com"}
HeaderGiá trị
x-sota-keyAPI key của bạn (sota_ek_…) — không phải secret
x-sota-timestampUnix giây, đúng giá trị đã ký. Request cũ quá 5 phút bị từ chối
x-sota-nonceDuy nhất theo từng request; trùng lặp bị coi là replay
x-sota-signaturev1= nối với HMAC dạng hex chữ thường

SotaAgents trả 201 kèm bao ngoài chuẩn — { success: true, data: { ticket, expiresAt }, timestamp }. Hãy bóc lớp bao: trả object data bên trong về cho trình duyệt, vì SDK đọc ticket ở cấp cao nhất và coi cả bao ngoài là ticket không hợp lệ — lỗi này biểu hiện thành 200 trên đường truyền và một khung chat quay mãi không lên. Response lỗi thì không được bọc; chúng về phẳng dạng { statusCode, code, message }, nên đọc code ở cấp cao nhất. Ticket sống 60 giây và chỉ đổi được một lần.

Giới hạn tần suất. Tạo ticket bị giới hạn 60 request mỗi phút cho mỗi API key và 120 request mỗi phút cho mỗi IP nguồn; bước đổi ticket bên trong iframe bị giới hạn 10 lần mỗi phút, tính theo cả IP người dùng cuối lẫn theo ticket. Vượt ngưỡng sẽ nhận 429 kèm Retry-After — hãy chuyển nguyên nó cho SDK để SDK tự giãn cách và thử lại, đừng gộp vào một lỗi 4xx chung. Toàn bộ backend của bạn dùng chung một IP nguồn, và mọi người sau cùng một NAT văn phòng dùng chung hạn mức đổi ticket, nên hãy ước lượng đợt đăng nhập cao điểm theo các con số này và báo trước cho chúng tôi nếu không đủ.

Frontend của bạn cần làm gì#

Nạp <script src="https://app.sotaagents.ai/embed/sdk.js"></script> rồi khởi tạo. Hai điểm quan trọng: lời gọi xin ticket phải kèm cookie phiên của bạn, và khi đăng xuất phải thu hồi phiên SotaAgents trước.

Origin trong các ví dụ dưới đây chỉ là minh hoạ. Người phụ trách SotaAgents sẽ cấp origin embed riêng cho tenant của bạn, và production khác staging — hãy để origin đó, URL script SDK và base URL của API cấp ticket trong cấu hình chứ không hard-code. SDK từ chối mọi embedUrl không phải HTTPS.

TypeScript
const embed = window.SotaEmbedSDK.createSotaEmbed({
  embedUrl: 'https://app.sotaagents.ai/embed',
  mode: 'bubble',
  getTicket: async () => {
    const response = await fetch('/api/sota/embed-ticket', {
      method: 'POST',
      credentials: 'include',
    });
    if (!response.ok) {
      // Status rides along so the SDK can fast-fail a 401 or 409 instead of
      // retrying a permanent failure four times.
      throw Object.assign(new Error('ticket_failed'), { status: response.status });
    }
    return response.json(); // { ticket, expiresAt }
  },
});

// On sign-out: revoke the SotaAgents session BEFORE clearing your own.
const revoked = await embed.logout();
if (!revoked) console.warn('SotaAgents session may still be active');
await fetch('/api/logout', { method: 'POST', credentials: 'include' });
Thu hồi phiên trước khi đăng xuất người dùng

Phiên SotaAgents nằm ở origin của embed, nên việc xoá phiên của bạn không đụng tới nó. Bỏ qua logout() thì người đăng nhập kế tiếp trên máy đó sẽ thừa hưởng một phiên còn sống — và rơi thẳng vào cuộc trò chuyện của người trước. Hãy gọi nó trước, rồi mới xoá phiên của bạn. Nó trả về false khi không xác nhận được việc thu hồi — vẫn cứ đăng xuất khỏi portal của bạn, nhưng trên máy dùng chung hoặc kiosk thì hãy báo cho người dùng biết phiên SotaAgents có thể vẫn còn sống.

Tuỳ biến theo thương hiệu của bạn#

Mọi tuỳ chọn ở đây đều không bắt buộc và mặc định đều là bản gốc của SotaAgents — không đặt gì thì giao diện y như hiện tại. Đặt vào thì khung chat mang tên bạn thay vì tên chúng tôi.

Code
createSotaEmbed({
  embedUrl: 'https://app.sotaagents.ai/embed',
  getTicket,

  // Inside the iframe
  language: 'vi',                  // 'en' | 'vi' | 'ja'
  branding: {
    logoUrl: 'https://intranet.acme.com/logo.svg',
    showUser: false,
    greeting: 'How can the Acme assistant help?',
    placeholder: 'Ask about policies, invoices, anything',
  },

  // The bubble chrome, drawn on your own page
  mode: 'bubble',
  title: 'Acme Assistant',
  primaryColor: '#0b5cff',
  position: 'bottom-left',
  width: 420,
  height: 640,
  openOnLoad: false,
});

Hai kiểu gắn. mode chỉ nhận đúng hai giá trị. 'bubble' là mặc định: SDK tự vẽ nút nổi và panel của nó lên trang bạn, còn container bị bỏ qua. 'inline' đặt thẳng iframe vào phần tử bạn chỉ định — không nút nổi, không panel — và ở đó container là bắt buộc, thiếu thì lệnh khởi tạo ném lỗi. Một khác biệt nên tính trước: bubble tạo iframe theo kiểu lười, tới lần bấm đầu tiên mới tạo, nên chưa ai mở chat thì chưa có gì xác thực; inline tạo ngay lúc tải trang, nên endpoint cấp ticket hỏng sẽ lộ ra lập tức và mỗi lượt xem trang tiêu một ticket.

Bên trong khung chat. Các giá trị này đi kèm URL của iframe chứ không chờ đăng nhập xong mới tới, nên khung hình đầu tiên người dùng thấy đã là của bạn — không có cảnh loé lên logo SotaAgents rồi mới đổi.

Tuỳ chọnMặc địnhTác dụng
languageTheo trình duyệten, vi hoặc ja (vn được chấp nhận như vi). Đây là điểm khởi đầu chứ không phải khoá cứng: người dùng đổi ngôn ngữ ngay trong khung chat thì giữ lựa chọn đó suốt phiên
branding.logoUrlWordmark SotaAgentsLogo của bạn ở sidebar và cạnh lời chào. Phải là URL http(s) tuyệt đối — khác đi thì bị bỏ qua và mặc định quay lại
branding.showLogotruefalse không hiển thị logo nào cả, và thắng logoUrl nếu đặt cả hai
branding.showUsertruefalse ẩn khối người dùng đang đăng nhập ở chân sidebar — hợp lý khi trang của bạn đã hiển thị ai đang đăng nhập
branding.greeting"How can I help you today?"Dòng chữ trên màn hình chat mới
branding.placeholder"Ask anything"Placeholder của ô soạn tin

Greeting và placeholder bị cắt còn 120 ký tự — đủ cho một câu, và đủ ngắn để không đẩy ô soạn tin ra khỏi màn hình.

Khung bubble. Nút nổi và panel do SDK vẽ trên trang của bạn, nằm ngoài iframe. Mọi thứ ở đây trừ title đều bị bỏ qua ở mode: 'inline', nơi container bạn truyền vào quyết định kích thước và trang của bạn quyết định phần xung quanh.

Tuỳ chọnMặc địnhTác dụng
titleSotaAgentsChữ trên header của panel — bỏ trống thì header chỉ còn các nút điều khiển. Nó cũng đặt tên iframe cho công nghệ trợ giúp, và nửa tác dụng này vẫn còn ở mode: 'inline', nên đây là hàng duy nhất trong bảng không chỉ dành cho bubble. Bỏ trống thì trình đọc màn hình đọc khung là SotaAgents; hãy đặt lại nếu bạn white-label
primaryColor#2f6bffNút nổi và header panel
positionbottom-rightHoặc bottom-left
width / height400 / 620Kích thước panel tính bằng px. Expand sẽ vượt qua mức này
zIndex2147483000Hạ xuống nếu panel cần nằm dưới một thành phần nào đó của bạn
openOnLoadfalsetrue mở panel ngay khi tải trang thay vì chờ người dùng bấm nút nổi

Hai tuỳ chọn nữa thuộc về chính iframe. allowMicrophone mặc định tắt; nhập bằng giọng nói cần bật. sandbox thay cho mặc định allow-scripts allow-same-origin allow-forms allow-popups — và ở đây nó ngược với quy ước HTML quen thuộc: sandbox: '' không phải mức hạn chế chặt nhất, mà gỡ bỏ hẳn thuộc tính, kéo theo mất luôn lớp cách ly của iframe. Muốn siết chặt hơn thì truyền danh sách token hẹp hơn, tuyệt đối không truyền chuỗi rỗng. Đừng động vào cả hai trừ khi có lý do.

Phục vụ logo qua HTTPS

Khung chat chạy trên HTTPS, nên trình duyệt tự nâng cấp logo http:// rồi chặn luôn: logo của bạn biến mất, logo SotaAgents quay lại, và không có request lỗi nào trong network tab để giải thích. Đây là cái bẫy branding hay gặp nhất — hãy kiểm tra trên một trang được phục vụ đúng như cách production sẽ phục vụ.

Khi embed chạy trên một site khác với cổng thông tin của bạn, cookie phiên của nó là cookie bên thứ ba.

Trình duyệtHành viKết quả
Chrome, EdgeGiữ cookie, phân vùng theo từng sitePhiên đầy đủ
FirefoxMặc định phân vùng cookie bên thứ baPhiên đầy đủ
SafariChặn cookie bên thứ baChuyển sang token 10 phút và tự xác thực lại

Cơ chế dự phòng vẫn chạy được, nhưng cứ vài phút lại phải xác thực lại. Phục vụ embed từ một subdomain của chính site bạn qua reverse proxy — ví dụ ai.congty.com đứng trước SotaAgents — sẽ biến cookie thành first-party và xoá bỏ vấn đề này trên mọi trình duyệt. Nếu làm vậy, nhớ hai điều: đừng để proxy sửa Set-Cookie, và phải tắt buffering vì câu trả lời của trợ lý được stream.

Kiểm tra trước khi go-live#

  • Secret nằm trong secret manager, không nằm trong source control.
  • Mọi người dùng dự kiến đều là thành viên đang hoạt động của tổ chức.
  • Endpoint cấp ticket chỉ đọc danh tính từ session.
  • logout() được gọi trước khi đăng xuất, trên mọi luồng kết thúc phiên.
  • Workspace còn đủ credit — hội thoại nhúng vẫn tiêu credit như mọi hội thoại khác.
  • Liên hệ bộ phận hỗ trợ để giới hạn những origin được phép nhúng embed về đúng tên miền của bạn.
Tài liệu SDK đầy đủ

Toàn bộ tuỳ chọn, callback và mã lỗi nằm trong Embed SDK integration guide, kèm một bản tích hợp mẫu chạy được. Liên hệ đầu mối SotaAgents của bạn để nhận tài liệu.

Mục lục

Esc

Tìm theo tiêu đề và nội dung trong mọi chương.