Tài liệu API
Tạo link tiếp thị và lấy đơn hàng phát sinh từ chính những link đó. Lấy khoá API
Bắt đầu
Địa chỉ gốc của mọi đường dẫn:
https://server.hoantien24h.vn/api/pub/v1Mọi lượt gọi phải mang khoá ở header X-Hoantien-Api-Key. Cũng nhận Authorization: Bearer ht_live_... nếu thư viện của bạn tiện hơn với cách đó.
curl https://server.hoantien24h.vn/api/pub/v1/me \
-H "X-Hoantien-Api-Key: ht_live_..."Đừng đặt khoá trong mã chạy ở trình duyệt. Khoá thay mặt tài khoản của bạn tạo link và đọc đơn. API cố ý không trả header chia sẻ tài nguyên giữa các gốc, nên gọi thẳng từ trình duyệt sẽ bị chặn. Hãy gọi từ máy chủ của bạn.
Khoá thử nghiệm
Khoá có tiền tố ht_test_ gọi được mọi đường dẫn nhưng khi tạo link sẽ trả về link giả, không đụng tới sàn và không tiêu hạn mức tạo link thật. Dùng nó khi viết và chạy thử mã, rồi mới đổi sang khoá ht_live_.
POST /links — tạo link
| Tham số | Bắt buộc | Mô tả |
|---|---|---|
url | Có | Đường dẫn sản phẩm. Nhận cả link rút gọn và cả đoạn văn bản chia sẻ có link nằm lẫn bên trong. Sàn được tự nhận, không cần khai. |
sub_id | Không | Mã theo dõi riêng của bạn, tối đa 255 ký tự. Dùng tiếng Việt có dấu, dấu cách, ký tự đặc biệt đều được: mã này không đi ra sàn mà được lưu ở hệ thống của chúng tôi. |
Gửi kèm header Idempotency-Key thì gọi lại cùng khoá đó trong 24 giờ sẽ trả về đúng link cũ thay vì tạo link mới. Nên dùng khi có cơ chế thử lại tự động.
curl -X POST https://server.hoantien24h.vn/api/pub/v1/links \
-H "X-Hoantien-Api-Key: ht_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: don-hang-8821" \
-d '{
"url": "https://shopee.vn/San-pham-i.123.456",
"sub_id": "nhóm zalo #7"
}'{
"success": true,
"link": {
"id": "A7bK2x9",
"url": "https://s.hoantien24h.vn/s/xY3mQ1p",
"raw_url": "https://s.shopee.vn/an_redir?...",
"original_url": "https://shopee.vn/San-pham-i.123.456",
"platform": "shopee",
"sub_id": "nhóm zalo #7",
"product": { "name": "...", "image": "...", "price": "₫199.000", "sold": "1200" },
"estimated_commission": { "text": "₫5.166", "amount": 5166 },
"clicks": 0,
"created_at": "2026-09-03T04:12:33.000Z"
}
}id là mã link, thứ nối link này với những đơn nó sinh ra. Hãy lưu lại nếu bạn muốn đối chiếu tới từng link.
estimated_commission là tiền của BẠN, đã trừ phần chia. Mức chi tiết tuỳ sàn: Shopee có cả text lẫn amount bằng số; TikTok Shop chỉ có textcòn amount là null; Shopee Food không có cả hai và cũng không trả thông tin món ăn. Đó là giới hạn của nguồn dữ liệu từng sàn chứ không phải thiếu sót, nên đừng dựng giao diện bắt buộc phải có amount.
GET /orders — lấy đơn hàng
Mặc định chỉ trả đơn phát sinh từ link bạn tạo qua API.
| Tham số | Mô tả |
|---|---|
sub_id | Lọc theo mã theo dõi của bạn. |
link_id | Lọc theo đúng một link. |
status | Một trong pending, confirmed, paid, cancelled. |
from | Từ ngày. 2026-09-01 được hiểu là cả ngày đó theo giờ Việt Nam. Gửi chuỗi ISO-8601 đầy đủ nếu cần chính xác hơn. |
to | Đến ngày, cùng quy ước. |
limit | Mỗi trang, tối đa 100, mặc định 20. |
cursor | Con trỏ trang sau, lấy từ paging.next_cursor. |
scope | Để all nếu muốn thấy cả đơn cá nhân của bạn, không chỉ đơn từ API. |
curl "https://server.hoantien24h.vn/api/pub/v1/orders?sub_id=nh%C3%B3m%20zalo%20%237&status=paid" \
-H "X-Hoantien-Api-Key: ht_live_..."{
"success": true,
"data": [
{
"id": "2605090JDW730P",
"status": "paid",
"platform": "shopee",
"sub_id": "nhóm zalo #7",
"link_id": "A7bK2x9",
"commission": 5166,
"items": [
{ "name": "...", "quantity": 2, "price": 100000, "amount": 200000, "shop": "Shop A" }
],
"purchased_at": "2026-08-20T02:11:00.000Z",
"confirmed_at": "2026-08-24T09:00:00.000Z",
"paid_at": "2026-09-05T01:00:00.000Z"
}
],
"paging": { "has_more": true, "next_cursor": "MTc1NjA..." }
}price là giá một đơn vị, amount là thành tiền cả dòng đã nhân số lượng và trừ khuyến mãi. Đừng nhân price với quantity để ra thành tiền.
Vòng đời một đơn, và vì sao tiền về chậm
Đây là phần hay gây hiểu nhầm nhất, nên nói thẳng: đơn không về theo thời gian thực.
| Trạng thái | Nghĩa là gì |
|---|---|
pending | Đơn đã ghi nhận, sàn chưa xác nhận hoàn thành. |
confirmed | Sàn đã báo hoàn thành. Bắt đầu chờ kỳ đối soát. |
paid | Tiền đã vào ví của bạn. Chỉ tới lúc này mới rút được. |
cancelled | Đơn bị huỷ hoặc hoàn. Có thể xảy ra sau khi đã từng hiện ra. |
Đơn Shopee được đồng bộ vài lần mỗi ngày nên đơn mới có độ trễ tính bằng giờ, không phải bằng giây. Từ lúc mua tới lúc tiền thật vào ví thường mất khoảng hai tuần rưỡi, vì còn phải chờ sàn đối soát và trả tiền.
Vì vậy đừng dựng hệ thống trả thưởng cho người dùng cuối dựa trên trạng thái pending. Một phần đơn sẽ chuyển thành cancelled.
GET /stats, /balance, /me
/stats?group_by=day hoặc group_by=sub_id trả về số đơn, hoa hồng, số đơn đã trả tiền và số đơn huỷ theo từng nhóm. Ngày được gom theo giờ Việt Nam.
/balance trả số dư khả dụng, số đang chờ và tổng đã rút, đơn vị đồng. /me trả trạng thái tài khoản, hạn mức đang áp và tỷ lệ hoa hồng của bạn.
Hạn mức
Hạn mức tính theo tài khoản, không theo từng khoá, nên tạo thêm khoá không làm tăng hạn mức. Mỗi phản hồi mang các header RateLimit-Limit, RateLimit-Remaining và RateLimit-Reset; hãy đọc chúng để tự điều tiết thay vì cứ gọi tới khi bị chặn.
Vượt hạn mức trả về mã 429 kèm errorCode: "RATE_LIMIT". Xem hạn mức hiện tại của bạn ở bảng điều khiển.
Lỗi
Mọi lỗi đều có cùng hình dạng, và errorCode là thứ nên dựa vào chứ không phải câu chữ:
{ "success": false, "errorCode": "RATE_LIMIT", "message": "..." }| Mã lỗi | Nghĩa |
|---|---|
MISSING_API_KEY | Không gửi khoá. |
INVALID_API_KEY | Khoá sai định dạng hoặc không tồn tại. |
KEY_REVOKED | Khoá đã bị thu hồi. |
HUB_NOT_ACTIVE | Tài khoản chưa được duyệt dùng API. |
HUB_BLOCKED | Quyền dùng API đã bị chặn, xem message để biết lý do. |
USER_BANNED | Tài khoản bị tạm khoá. |
RATE_LIMIT | Vượt hạn mức, xem header RateLimit-Reset. |
MISSING_URL | Thiếu trường url khi tạo link. |
UNKNOWN_PLATFORM | Không nhận ra link thuộc sàn nào. |
INVALID_SHOPEE_URL | Link Shopee không phải link sản phẩm, ví dụ link video hoặc trang shop. |
PRODUCT_NOT_SUPPORTED | Sản phẩm này không tạo được link tiếp thị. |
ORDER_NOT_FOUND | Không có đơn đó, hoặc đơn không thuộc về bạn. |
UPSTREAM_ERROR | Sàn hoặc nhà cung cấp trung gian đang lỗi. Thử lại sau. |
Các sàn được hỗ trợ
API hiện phục vụ ba sàn. Link của sàn khác sẽ bị từ chối với mã UNKNOWN_PLATFORM.
| Sàn | Tên miền nhận được | Đếm lượt bấm | Số tiền hoa hồng |
|---|---|---|---|
| Shopee | shopee.vn, shp.ee, shope.ee | Có | Có, amount là số đồng |
| Shopee Food | spf.shopee.vn, shopeefood.vn | Có | Không, và cũng không có thông tin món ăn |
| TikTok Shop | tiktok.com | Có | text là số đồng, amount là null |
Không cần khai sàn khi tạo link, hệ thống tự nhận từ đường dẫn. Nhận cả link rút gọn lẫn cả đoạn văn bản chia sẻ có link nằm lẫn bên trong, kiểu nút Chia sẻ của Shopee chép ra nguyên một đoạn quảng cáo.
Đúng sàn vẫn chưa đủ, phải đúng link sản phẩm. Link trang chủ, trang tìm kiếm, trang cửa hàng, link video và link phát trực tiếp đều bị từ chối với mã INVALID_SHOPEE_URL kèm trường kind cho biết dán nhầm loại nào. Riêng link video và phát trực tiếp bị chặn có chủ ý: chúng không sinh ra hoa hồng.
TikTok Shop chặt hơn hẳn: chỉ tạo được link cho sản phẩm của shop đã mở hợp tác, nên tỷ lệ bị từ chối với mã PRODUCT_NOT_SUPPORTED khá cao. Đây là giới hạn của bên cung cấp, không phải của chúng tôi, và không có cách nào biết trước một sản phẩm có tạo được link hay không ngoài việc thử.
Webhook: nhận đơn ngay, khỏi hỏi định kỳ
Khai một địa chỉ https ở bảng điều khiển. Mỗi khi có đơn mới hoặc đơn đổi trạng thái, chúng tôi gửi ngay tới đó, không phải chờ bạn hỏi.
| Sự kiện | Khi nào bắn |
|---|---|
order.created | Đơn lần đầu được ghi nhận. |
order.updated | Đơn đổi trạng thái, kể cả lúc chuyển sang paid tức tiền đã vào ví. |
Thân gửi tới có dạng:
{
"event": "order.created",
"event_id": "9f2c...",
"created_at": "2026-09-03T04:30:57.705Z",
"data": { ... giống hệt một phần tử của GET /orders ... }
}Kiểm chữ ký trước khi tin. Băm HMAC-SHA256 chuỗi <X-Hoantien-Timestamp>.<thân thô> bằng khoá ký của bạn, rồi so với header X-Hoantien-Signature.
// Express: PHẢI lấy thân THÔ, không dùng express.json() cho đường này
app.post('/webhook/hoantien24h', express.raw({ type: '*/*' }), (req, res) => {
const moc = req.get('X-Hoantien-Timestamp')
const kyMongDoi = 'sha256=' + crypto
.createHmac('sha256', process.env.HOANTIEN_WEBHOOK_SECRET)
.update(moc + '.' + req.body.toString())
.digest('hex')
if (kyMongDoi !== req.get('X-Hoantien-Signature')) return res.sendStatus(403)
res.sendStatus(200) // trả 2xx TRƯỚC, xử lý sau
const su = JSON.parse(req.body.toString())
xuLy(su) // nhớ chống trùng theo su.event_id
})Hai chỗ dễ sai: băm trên thân đã qua bộ phân tích JSON (thứ tự khoá và khoảng trắng đổi, chữ ký đúng cũng thành sai), và quên rằng mốc thời gian nằm TRƯỚC dấu chấm.
Trả 2xx trong vòng 8 giây. Quá hạn hoặc trả mã lỗi thì chúng tôi thử lại 5 lần, giãn dần 1 phút, 5 phút, 30 phút, 2 giờ, 6 giờ. Nên trả 2xx ngay rồi xử lý bất đồng bộ, đừng để chúng tôi chờ bạn ghi cơ sở dữ liệu xong.
Hỏng liên tiếp 20 lượt thì địa chỉ tự bị tắt, và bạn đọc được lý do ở bảng điều khiển. Bật lại bất cứ lúc nào.
Cùng một sự kiện có thể tới hai lần. Hãy chống trùng theo event_id, đừng dựa vào việc chúng tôi gửi đúng một lần. Sự kiện thử gửi từ nút “Gửi thử” mang thêm "test": true, đừng cộng nó vào sổ sách.
Địa chỉ nhận phải là https và không được trỏ vào mạng nội bộ hay địa chỉ vòng lặp. Đây là chỗ máy chủ của chúng tôi tự gọi vào, nên mở cho địa chỉ nội bộ là mở đường dò quét mạng riêng.
Vẫn nên hỏi định kỳ một nhịp thưa
Webhook không thay thế hoàn toàn GET /orders. Nếu máy chủ của bạn chết quá 9 tiếng thì chúng tôi đã bỏ cuộc và sự kiện đó mất luôn. Một lượt quét đối soát mỗi vài giờ là đủ để bắt lại phần thiếu.