REST API Hỗ trợ
REST API của Hỗ trợ cho server của bạn làm việc với hộp thư như một nhân viên: liệt kê hội thoại từ mọi kênh, đọc dòng thời gian, trả lời hoặc ghi chú nội bộ, giao việc, gắn tag và đánh dấu đã xử lý. Dùng nó để nối hộp thư với ERP, CRM, hệ thống đơn hàng hay bot của riêng bạn. Webhook báo cho bạn khi có chuyện xảy ra, nên không cần gọi API liên tục để dò.
Trước khi bắt đầu
-
Một API key tạo trong dashboard (API Keys). Gửi trong header
Authorization: Bearer ht_...tớihttps://api.hatang.dev. Xem Xác thực. -
Key cần đúng scope:
Scope Cho phép chat:readLiệt kê, đọc site, hội thoại, dòng thời gian, khách truy cập, phiếu hỗ trợ, báo cáo, kịch bản, chu trình, chiến dịch, câu trả lời mẫu, quy tắc phân phối và nguồn kiến thức chat:writeMọi thứ của chat:read, cộng với trả lời, cập nhật hội thoại, phiếu, site, kịch bản, chu trình, chiến dịch, câu trả lời mẫu, quy tắc phân phối và nguồn kiến thứccrm:read/crm:writeKhách hàng, trường tuỳ chỉnh của khách hàng và Zalo ZNS (key có scope chat:*cũng dùng được)products:read/products:writeSản phẩm và đơn hàng (key có scope chat:*cũng dùng được)Scope
writebao gồmread;chat:*và*bao trùm tất cả. -
Module Hỗ trợ phải đang bật cho doanh nghiệp. Khi module tắt, mọi endpoint trả
403 product_disabled(Bật/tắt module).
chat:reply và chat:manage là quyền của nhân viên trong dashboard (Nhân viên và phân quyền), không phải scope của API key.
1. Liệt kê hội thoại
GET /chat/conversations trả về hội thoại có hoạt động mới nhất trước, mặc định 50 hội thoại mỗi trang (tối đa 100).
| Tham số query | Ý nghĩa |
|---|---|
status | Phân tách bằng dấu phẩy: open, pending, resolved, closed |
unanswered | true: chỉ hội thoại mà khách là người nhắn cuối |
assignee_id | Hội thoại đang giao cho nhân viên này |
site_id | Hội thoại của một site |
visitor_id | Hội thoại của một khách truy cập |
q | Từ khoá tìm kiếm (tối đa 200 ký tự) |
flag | hidden (đang tạm ẩn), spam hoặc none. Mặc định chỉ trả hội thoại không gắn cờ |
limit | 1–100, mặc định 50 |
after | Con trỏ: id của hội thoại cuối cùng ở trang trước |
- cURL
- Node.js
- Python
- PHP
curl "https://api.hatang.dev/chat/conversations?status=open,pending&unanswered=true&limit=20" \
-H "Authorization: Bearer ht_123..."
import { Hatang } from "@hatang/sdk";
const hatang = new Hatang("ht_123...");
const { data, error } = await hatang.chat.conversations.list({
status: ["open", "pending"],
unanswered: true,
limit: 20,
});
if (error) throw new Error(error.message);
console.log(data.data.map((c) => `${c.id} ${c.channel} ${c.last_message_preview}`));
from hatang import Hatang
hatang = Hatang("ht_123...")
page = hatang.chat.conversations.list(status=["open", "pending"], unanswered=True, limit=20)
for c in page["data"]:
print(c["id"], c["channel"], c["last_message_preview"])
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.hatang.dev']);
$res = $client->get('/chat/conversations', [
'headers' => ['Authorization' => 'Bearer ht_123...'],
'query' => ['status' => 'open,pending', 'unanswered' => 'true', 'limit' => 20],
]);
$page = json_decode((string) $res->getBody(), true);
{
"object": "list",
"has_more": true,
"data": [
{
"id": "cnv_...",
"site_id": "site_...",
"visitor_id": "vis_...",
"channel": "zalo",
"channel_account_id": "...",
"status": "open",
"assignee_kind": null,
"assignee_id": null,
"seq": 14,
"agent_unread": 2,
"visitor_unread": 0,
"last_message_at": "2026-10-23T08:15:02.000Z",
"last_message_preview": "Shop còn size M không ạ?",
"last_message_sender_type": "visitor",
"first_response_at": null,
"tags": [],
"member_ids": [],
"flag": null,
"subject": null,
"created_at": "2026-10-23T08:14:40.000Z",
"updated_at": "2026-10-23T08:15:02.000Z",
"visitor": { "id": "vis_...", "name": "Lan", "...": "..." }
}
]
}
channel là một trong web, zalo, zalo_personal, messenger, instagram, tiktok, tiktokshop, whatsapp, telegram, x, line hoặc email. Hội thoại có cùng các trường này ở mọi nơi, kể cả trong webhook.
Tham số boolean trên query được hiểu là "có mặt nghĩa là true". Gửi unanswered=true để lọc, còn không thì bỏ hẳn tham số; unanswered=false không tắt bộ lọc.
2. Đọc dòng thời gian
GET /chat/conversations/{id}/messages trả về tin nhắn và sự kiện theo thứ tự seq. Mỗi phần tử có kind: "message" hoặc kind: "event".
| Tham số query | Ý nghĩa |
|---|---|
after_seq | Trả các phần tử sau seq này (cũ trước). Không truyền thì nhận trang mới nhất |
limit | 1–200, mặc định 100 |
include_private | true để lấy cả ghi chú nội bộ. Bỏ trống để không lấy |
- cURL
- Node.js
- Python
- PHP
curl "https://api.hatang.dev/chat/conversations/cnv_123/messages?after_seq=10" \
-H "Authorization: Bearer ht_123..."
const { data } = await hatang.chat.conversations.messages("cnv_123", { after_seq: 10 });
for (const item of data.data) {
if (item.kind === "message") console.log(item.seq, item.sender_type, item.content);
}
items = hatang.chat.conversations.messages("cnv_123", after_seq=10)["data"]
$res = $client->get('/chat/conversations/cnv_123/messages', [
'headers' => ['Authorization' => 'Bearer ht_123...'],
'query' => ['after_seq' => 10],
]);
{
"kind": "message",
"id": "msg_...",
"conversation_id": "cnv_123",
"seq": 11,
"sender_type": "visitor",
"sender_id": "vis_...",
"sender_name": "Lan",
"visibility": "public",
"content_type": "text",
"content": "Shop còn size M không ạ?",
"attachments": [],
"metadata": {},
"delivery_status": null,
"delivery_error": null,
"read_at": null,
"created_at": "2026-10-23T08:15:02.000Z"
}
sender_type là visitor, agent, bot hoặc system. Tệp đính kèm có url ký sẵn, dùng được trong 24 giờ; hết hạn thì đọc lại tin để có link mới.
3. Trả lời hoặc ghi chú
POST /chat/conversations/{id}/messages gửi một tin dưới tên đội của bạn. Hạ Tầng chuyển tin qua đúng kênh của hội thoại: widget, Zalo, Messenger, email…
| Trường | Kiểu | Ghi chú |
|---|---|---|
content | string | Bắt buộc, 1–10.000 ký tự |
visibility | public | private | Mặc định public. private là ghi chú nội bộ, khách không thấy |
sender_name | string | Tên khách nhìn thấy, tối đa 100 ký tự. Mặc định Support |
attachments | string[] | Tối đa 5 id của tệp đã tải lên hội thoại |
metadata | object | Thẻ (product, order, payment, quick_replies), tối đa 4 KB |
Gửi kèm header Idempotency-Key để thử lại an toàn. Request thứ hai cùng key trong cùng hội thoại trả về tin gốc với mã 200 và "duplicate": true thay vì gửi hai lần.
- cURL
- Node.js
- Python
- PHP
curl -X POST "https://api.hatang.dev/chat/conversations/cnv_123/messages" \
-H "Authorization: Bearer ht_123..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-8812-shipped" \
-d '{"content": "Đơn #8812 đã giao cho đơn vị vận chuyển, dự kiến tới vào thứ Sáu.", "sender_name": "Shop Trà Xanh"}'
const { data, error } = await hatang.chat.conversations.send(
"cnv_123",
{ content: "Đơn #8812 đã giao cho đơn vị vận chuyển, dự kiến tới vào thứ Sáu.", sender_name: "Shop Trà Xanh" },
{ idempotencyKey: "order-8812-shipped" },
);
hatang.chat.conversations.send(
"cnv_123",
{"content": "Đơn #8812 đã giao cho đơn vị vận chuyển, dự kiến tới vào thứ Sáu.", "sender_name": "Shop Trà Xanh"},
idempotency_key="order-8812-shipped",
)
$res = $client->post('/chat/conversations/cnv_123/messages', [
'headers' => ['Authorization' => 'Bearer ht_123...', 'Idempotency-Key' => 'order-8812-shipped'],
'json' => ['content' => 'Đơn #8812 đã giao cho đơn vị vận chuyển, dự kiến tới vào thứ Sáu.', 'sender_name' => 'Shop Trà Xanh'],
]);
{ "object": "chat_message", "id": "msg_...", "seq": 15, "conversation_id": "cnv_123", "duplicate": false }
Lỗi có thể gặp:
409 conversation_closed: hội thoại đãclosed, không nhận thêm tin.422 validation_error:contenttrống, quá dài, hoặcmetadatavượt 4 KB.- Trên kênh mạng xã hội, việc gửi diễn ra bất đồng bộ. API nhận tin ngay; nếu sau đó nền tảng từ chối (ví dụ đã quá cửa sổ 24 giờ của Messenger hay WhatsApp), tin có
delivery_status: "failed"kèmdelivery_error. Đọc lại dòng thời gian hoặc xem trong hộp thư để biết. Trang của từng kênh giải thích quy định riêng của nền tảng đó.
Để gửi thẻ sản phẩm, dùng POST /chat/conversations/{id}/products với product_ids (tối đa 10) và intro tuỳ chọn.
4. Giao việc, gắn tag, tạm ẩn và xử lý xong
PATCH /chat/conversations/{id} đổi một hoặc nhiều trường:
| Trường | Giá trị |
|---|---|
status | open, pending, resolved, closed |
assignee_id | User id của nhân viên, hoặc null để bỏ giao |
tags | Toàn bộ danh sách tag (thay danh sách hiện tại; tối đa 20 tag, mỗi tag tối đa 40 ký tự) |
member_ids | Toàn bộ danh sách nhân viên cùng theo dõi hội thoại (tối đa 20) |
flag | hidden (tạm ẩn), spam, hoặc null để bỏ cờ |
- cURL
- Node.js
- Python
- PHP
curl -X PATCH "https://api.hatang.dev/chat/conversations/cnv_123" \
-H "Authorization: Bearer ht_123..." \
-H "Content-Type: application/json" \
-d '{"status": "resolved", "tags": ["shipping"]}'
await hatang.chat.conversations.update("cnv_123", { status: "resolved", tags: ["shipping"] });
hatang.chat.conversations.update("cnv_123", {"status": "resolved", "tags": ["shipping"]})
$client->patch('/chat/conversations/cnv_123', [
'headers' => ['Authorization' => 'Bearer ht_123...'],
'json' => ['status' => 'resolved', 'tags' => ['shipping']],
]);
Response là hội thoại sau khi cập nhật. Mỗi thay đổi được ghi thành một sự kiện trên dòng thời gian, đứng tên API key.
5. Nhận thông báo bằng webhook
Tạo endpoint webhook (dashboard Webhooks, hoặc POST /webhooks) và chọn các sự kiện Hỗ trợ cần nhận. Mỗi lần gửi có dạng { "type", "created_at", "data" }, ký như mọi webhook khác của Hạ Tầng (Webhook).
| Sự kiện | Khi nào |
|---|---|
chat.conversation.created | Có hội thoại mới trên bất kỳ kênh nào |
chat.conversation.assigned | Người phụ trách thay đổi |
chat.conversation.resolved, chat.conversation.closed, chat.conversation.reopened | Trạng thái thay đổi |
chat.conversation.handoff | Trợ lý AI chuyển hội thoại cho người |
chat.conversation.tagged | Có tag mới (mỗi tag mới một sự kiện, kèm tag) |
chat.message.created | Có tin công khai mới, từ khách, nhân viên, bot hoặc API |
chat.visitor.identified | Một khách truy cập được nhận diện |
chat.ticket.created, chat.ticket.updated, chat.ticket.resolved, chat.ticket.breached | Phiếu hỗ trợ thay đổi, quá hạn SLA |
chat.rating.created | Khách đánh giá hội thoại |
chat.order.created, chat.order.updated, chat.order.paid | Đơn hàng |
chat.flow.webhook, chat.campaign.lead | Bước webhook của kịch bản chạy; form chiến dịch thu được lead |
chat.zns.sent, chat.zns.failed | Kết quả gửi Zalo ZNS |
chat.message.created mang nội dung tin cùng một object conversation. Ghi chú nội bộ không bắn sự kiện này.
chat.message.created bắn cả cho tin bạn vừa gửi. Nếu bot trả lời theo sự kiện này, chỉ xử lý khi data.sender_type là visitor.
6. Liên kết tới trang quản trị của bạn
Liên kết hồ sơ đặt URL của bạn cạnh mỗi hội thoại, điền theo người trong hội thoại đó. PUT /chat/profile-links (scope chat:write) thay toàn bộ danh sách; GET /chat/profile-links (chat:read) trả về danh sách.
- cURL
- Node.js
- Python
- PHP
curl -X PUT "https://api.hatang.dev/chat/profile-links" \
-H "Authorization: Bearer ht_123..." \
-H "Content-Type: application/json" \
-d '{"data": [{"label": "Admin", "url": "https://example.com/admin/users/{{user_id}}"}, {"label": "Orders", "url": "https://example.com/admin/orders?email={{email}}"}]}'
await hatang.chat.profileLinks.set([
{ label: "Admin", url: "https://example.com/admin/users/{{user_id}}" },
{ label: "Orders", url: "https://example.com/admin/orders?email={{email}}" },
]);
requests.put(
"https://api.hatang.dev/chat/profile-links",
headers={"Authorization": "Bearer ht_123..."},
json={"data": [
{"label": "Admin", "url": "https://example.com/admin/users/{{user_id}}"},
{"label": "Orders", "url": "https://example.com/admin/orders?email={{email}}"},
]},
)
$client->put('/chat/profile-links', [
'headers' => ['Authorization' => 'Bearer ht_123...'],
'json' => ['data' => [
['label' => 'Admin', 'url' => 'https://example.com/admin/users/{{user_id}}'],
['label' => 'Orders', 'url' => 'https://example.com/admin/orders?email={{email}}'],
]],
]);
Phản hồi là { "object": "list", "data": [...] }, mỗi liên kết có id (pl_…); gửi lại id để giữ nguyên liên kết. Thêm site_ids để chỉ hiện liên kết trong hội thoại của một số site.
| Biến | Giá trị |
|---|---|
{{user_id}} | user_id đã xác minh từ identify có chữ ký; nếu không, là id khách tự khai và nhân viên thấy liên kết bị đánh dấu chưa xác minh (Nhận diện khách) |
{{email}}, {{phone}}, {{name}} | Thông tin liên hệ của khách |
{{customer_id}}, {{conversation_id}}, {{site_id}} | Id trong Hạ Tầng |
{{field.<key>}} | Một trường thông tin khách hàng |
{{data.<key>}} | Dữ liệu tuỳ chỉnh của khách (luôn bị đánh dấu chưa xác minh) |
Quy tắc: chỉ https://, biến chỉ đứng sau tên miền (không bao giờ trong tên miền), mỗi URL tối đa 500 ký tự, tối đa 10 liên kết. Sai quy tắc trả 422 validation_error. Giá trị được mã hoá URL, và liên kết có biến không có giá trị sẽ không hiện.
Các endpoint khác
Cùng API này bao phủ phần còn lại của Hỗ trợ. Mọi đường dẫn bắt đầu bằng https://api.hatang.dev/chat:
| Nhóm | Đường dẫn |
|---|---|
| Site | /sites, /sites/{id}, /sites/{id}/identity-secret |
| Khách truy cập và bảng Đang truy cập | /visitors/{id}, /visitors/{id}/conversations, /live, /live/greet |
| Khách hàng | /customers, /customers/{id}, /customers/{id}/merge, /customers/{id}/conversations, /customers/export.csv, /customer-fields |
| Phiếu hỗ trợ và SLA | /tickets, /tickets/stats, /tickets/{id}, /sla |
| Liên kết hồ sơ | /profile-links |
| Tự động hoá | /flows, /workflows, /campaigns, /routing-rules, /canned-responses |
| Kiến thức cho AI | /sites/{id}/kb/sources, /sites/{id}/kb/files, /sites/{id}/kb/search |
| Bán hàng | /products, /orders, /orders/{id}/payment-link |
| Zalo ZNS | /zns/templates, /zns, /zns/bulk |
| Báo cáo | /reports, /ratings |
Mọi request và response có trong tham chiếu REST API. SDK Node.js bọc phần lớn các endpoint này trong hatang.chat.*; xem SDK Node.js.
Lỗi và giới hạn
Lỗi dùng chung dạng { "statusCode", "name", "message" }. Rẽ nhánh theo name, không theo message.
| Mã | name | Nguyên nhân |
|---|---|---|
| 401 | missing_api_key | Thiếu header Authorization |
| 403 | invalid_api_key | Key không tồn tại hoặc đã thu hồi |
| 401 | restricted_api_key | Key thiếu scope cho endpoint này |
| 403 | product_disabled | Module Hỗ trợ đang tắt |
| 404 | not_found | Id không tồn tại trong doanh nghiệp này |
| 409 | conversation_closed | Gửi tin vào hội thoại đã đóng |
| 422 | validation_error | Body hoặc query không hợp lệ |
| 429 | rate_limit_exceeded | Vượt giới hạn tần suất |
Các endpoint Hỗ trợ cho phép 50 request mỗi giây cho mỗi doanh nghiệp. Xem Lỗi và Giới hạn.
Bước tiếp theo
- Khái niệm và luồng tin nhắn
- Tự động hoá: kịch bản, chu trình và quy tắc phân phối, cũng quản lý được qua API
- Realtime: widget và ứng dụng nhân viên nói chuyện với gateway thế nào
- Webhook: xác minh chữ ký và xử lý gửi lại