Chuẩn tích hợp kênh
Tài liệu này mô tả chuẩn giao tiếp giữa SieuTroly và các adapter kênh bên ngoài (Facebook, Telegram, Zalo, Instagram, v.v.).
Mỗi platform cần xây dựng một adapter nhỏ làm cầu nối:
- Nhận webhook từ platform → chuẩn hóa → gửi vào SieuTroly
- Nhận reply từ SieuTroly → gửi tin nhắn ra platform
Luồng tổng quan
Platform (FB / TG / ...) ──incoming──▶ Adapter ──▶ POST /webhooks/message (SieuTroly)
│
Platform (FB / TG / ...) ◀──outgoing── Adapter ◀── POST reply_url (SieuTroly → Adapter)Cấu hình kênh trong SieuTroly
Khi tạo kênh (channel), trường config lưu JSON với các key:
| Key | Bắt buộc | Mô tả |
|---|---|---|
webhook_token | Có | Token bí mật dùng để xác thực request 2 chiều |
reply_url | Có | URL của adapter nhận tin nhắn đi từ SieuTroly |
Ví dụ config:
{
"webhook_token": "sk_abc123xyz",
"reply_url": "https://fb-adapter.example.com/reply"
}Tin nhắn ĐẾN (Adapter → SieuTroly)
Endpoint: POST https://api.sieutroly.ai/webhooks/message
Header:
Content-Type: application/json
X-SieuTroly-Token: <webhook_token>Body:
{
"channel_id": "uuid-channel-trong-sieutroly",
"sender": {
"id": "platform-user-id (nếu is_group=true thì đây là ID của NHÓM)",
"name": "Nguyễn Văn A",
"avatar": "https://...",
"is_group": false
},
"message": {
"id": "platform-message-id",
"type": "text",
"content": "Xin chào!",
"media_url": null,
"timestamp": "2024-01-15T08:30:00Z",
"sender_type": "contact",
"reply_to": {
"external_id": "platform-message-id-của-tin-gốc",
"preview": "Nội dung tin gốc (fallback nếu không tìm thấy trong DB)",
"sender_name": "Tên người gửi tin gốc (tuỳ chọn)"
},
"actor": {
"id": "platform-user-id-của-thành-viên-vừa-gửi",
"name": "Tên thành viên"
}
}
}message.sender_type (tuỳ chọn, mặc định "contact"): set "agent" khi adapter phát hiện tin nhắn này thực ra do AGENT tự gửi qua app gốc của platform (vd trả lời trực tiếp bằng điện thoại Zalo, không qua SieuTroly) — lúc đó sender vẫn phải là thông tin ĐỐI PHƯƠNG trong hội thoại (không phải agent), để tin nhắn được gắn đúng vào hội thoại và hiển thị đúng chiều trong Inbox.
sender.is_group (tuỳ chọn, mặc định false): set true khi tin đến từ 1 nhóm chat — lúc đó sender.id/name/avatar là của CHÍNH cái nhóm (mọi tin trong nhóm gộp vào 1 conversation), còn message.actor (tuỳ chọn) là thành viên cụ thể vừa gửi tin đó, chỉ dùng để hiển thị tên người gửi trong Inbox — không tạo Customer riêng cho actor.
Chiều ngược lại (mục 4), recipient.is_group cũng phải set đúng để adapter biết gửi vào nhóm hay chat riêng (SieuTroly tự điền theo contact.is_group đã lưu khi nhận tin đến, adapter không cần tự suy luận).
message.reply_to (tuỳ chọn): set khi tin nhắn này là reply/quote 1 tin khác trên platform. SieuTroly tự tìm tin gốc trong DB theo {channel_id, external_id: reply_to.external_id} — tìm thấy thì hiển thị liên kết tới tin gốc thật, không thấy thì fallback hiển thị theo reply_to.preview/reply_to.sender_name (tin gốc có thể đã cũ, ngoài phạm vi lưu trữ, hoặc gửi trước khi tích hợp).
Các giá trị message.type:
| type | Mô tả | content | media_url |
|---|---|---|---|
text | Tin nhắn văn bản | Nội dung văn bản | null |
image | Ảnh | Caption hoặc [Ảnh] | URL ảnh |
video | Video | Caption hoặc [Video] | URL video |
audio | Tin nhắn thoại | [Audio] | URL audio |
file | File đính kèm | Tên file | URL file |
sticker | Sticker / Emoji đặc biệt | [Sticker] | URL sticker |
Response thành công (200):
{
"ok": true,
"conversation_id": "uuid",
"message_id": "uuid"
}Response lỗi:
{
"error": "Mô tả lỗi"
}Tin nhắn ĐI (SieuTroly → Adapter)
Khi agent gửi tin nhắn, SieuTroly tự động gọi reply_url của adapter.
Method: POST <reply_url>
Header:
Content-Type: application/json
X-SieuTroly-Token: <webhook_token>Body:
{
"conversation_id": "uuid",
"recipient": {
"id": "platform-user-id (id nhóm nếu is_group=true)",
"name": "Nguyễn Văn A",
"is_group": false
},
"message": {
"type": "text",
"content": "Chào bạn, tôi có thể giúp gì cho bạn?",
"media_url": null
},
"sent_at": "2024-01-15T08:31:00Z"
}Adapter phải trả về HTTP 200 để SieuTroly biết đã nhận. Nếu thất bại, lỗi được ghi log nhưng không ảnh hưởng tin nhắn trong hệ thống.
Xác thực (Authentication)
Cả 2 chiều đều dùng header X-SieuTroly-Token.
- Adapter → SieuTroly: Adapter đặt token vào header khi gọi
/webhooks/message - SieuTroly → Adapter: SieuTroly đặt cùng token khi gọi
reply_url
Adapter phải xác minh token trước khi xử lý reply để tránh request giả mạo.
Ví dụ triển khai adapter (Telegram)
// telegram-adapter/index.js
const express = require('express')
const app = express()
app.use(express.json())
const SIEUTROLY_URL = 'https://api.sieutroly.ai/webhooks/message'
const CHANNEL_ID = 'uuid-channel-telegram-trong-sieutroly'
const TOKEN = 'sk_abc123xyz'
const BOT_TOKEN = 'telegram-bot-token'
// 1. Nhận webhook từ Telegram → gửi vào SieuTroly
app.post('/telegram-webhook', async (req, res) => {
const msg = req.body.message
if (!msg?.text) return res.sendStatus(200)
await fetch(SIEUTROLY_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-SieuTroly-Token': TOKEN },
body: JSON.stringify({
channel_id: CHANNEL_ID,
sender: {
id: String(msg.chat.id),
name: [msg.from.first_name, msg.from.last_name].filter(Boolean).join(' '),
},
message: {
id: String(msg.message_id),
type: 'text',
content: msg.text,
timestamp: new Date(msg.date * 1000).toISOString(),
},
}),
})
res.sendStatus(200)
})
// 2. Nhận reply từ SieuTroly → gửi ra Telegram
app.post('/reply', async (req, res) => {
if (req.headers['x-sieutroly-token'] !== TOKEN) return res.sendStatus(401)
const { recipient, message } = req.body
await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chat_id: recipient.id, text: message.content }),
})
res.sendStatus(200)
})
app.listen(3001)Ví dụ triển khai adapter (Facebook Messenger)
// facebook-adapter/index.js
const FB_ACCESS_TOKEN = 'EAAxxxxxxx'
const PAGE_VERIFY_TOKEN = 'sieutroly_verify'
// Xác thực webhook Facebook
app.get('/facebook-webhook', (req, res) => {
if (req.query['hub.verify_token'] === PAGE_VERIFY_TOKEN)
return res.send(req.query['hub.challenge'])
res.sendStatus(403)
})
// 1. Nhận webhook từ Facebook → gửi vào SieuTroly
app.post('/facebook-webhook', async (req, res) => {
for (const entry of req.body.entry || []) {
for (const event of entry.messaging || []) {
if (!event.message || event.message.is_echo) continue
await fetch(SIEUTROLY_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-SieuTroly-Token': TOKEN },
body: JSON.stringify({
channel_id: CHANNEL_ID,
sender: { id: event.sender.id, name: null },
message: {
id: event.message.mid,
type: event.message.attachments ? event.message.attachments[0].type : 'text',
content: event.message.text || '[Media]',
media_url: event.message.attachments?.[0]?.payload?.url || null,
timestamp: new Date(event.timestamp).toISOString(),
},
}),
})
}
}
res.sendStatus(200)
})
// 2. Nhận reply từ SieuTroly → gửi ra Facebook
app.post('/reply', async (req, res) => {
if (req.headers['x-sieutroly-token'] !== TOKEN) return res.sendStatus(401)
const { recipient, message } = req.body
await fetch(`https://graph.facebook.com/v19.0/me/messages?access_token=${FB_ACCESS_TOKEN}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messaging_type: 'RESPONSE',
recipient: { id: recipient.id },
message: message.type === 'image'
? { attachment: { type: 'image', payload: { url: message.media_url } } }
: { text: message.content },
}),
})
res.sendStatus(200)
})Checklist cho bên tích hợp
- [ ] Đăng ký kênh trên SieuTroly, lấy
channel_id - [ ] Cấu hình
webhook_tokenvàreply_urltrong config kênh - [ ] Deploy adapter, nhận webhook từ platform
- [ ] Chuẩn hóa payload và POST vào
POST /webhooks/message - [ ] Implement endpoint nhận
reply_url, xác thực token, gửi tin ra platform - [ ] Test end-to-end: gửi tin từ platform → hiện trong SieuTroly → agent reply → hiện lại trên platform
