Cộng đồng kiếm tiền Affiliate TikTok Shop — chia sẻ mẹo & chiến lược Group Facebook Zalo

Developer API — TikTok Shop Affiliate

Tạo link affiliate, lấy đơn hàng & hoa hồng, tìm sản phẩm và nhận postback ngay từ hệ thống của bạn.

Cập nhật 02/11/2026 REST · JSON · 7 endpoint Postback ký HMAC 300 req/phút
Có gì mới Cập nhật 02/11/2026 · bấm để xem
Chi tiết ở mục Hướng dẫn API. Các endpoint và field cũ không thay đổi.
  • 02/11/2026 — Endpoint mới GET /live-sessions: danh sách ca live của creator (số đơn, GMV, hoa hồng, đơn huỷ, giờ lên live, CTV đứng live). API đơn hàng thêm field live_session_id và lọc được bằng ?live_session_id=.
  • 09/10/2026 — Postback và API đơn hàng thêm trace_id + trace_type (lọc đơn được bằng ?trace_id=): theo dõi từng click bằng cách gắn event_id vào link /general-links (đơn về có trace_type = GENERAL, trace_id = mã bạn gắn); link /links có trace_type = SPECIFIC, trace_id = sub_id. Code mẫu ở phần 6.
  • 06/10/2026 — Tích hợp bằng AI: chọn việc + ngôn ngữ để sinh prompt, link spec công khai /tiktok-shop-developer/ai-spec cho AI agent; thêm mẫu xác minh chữ ký postback Node.js / Python. Trang tài liệu xem được không cần đăng nhập.
  • 03/09/2026 — GET /partner/tiktok/affiliate/products/search: tìm sản phẩm theo tên khi chưa biết product_id. Lọc được giá / hoa hồng / lượt bán / điểm shop / danh mục; phân trang bằng con trỏ next_page_token. Kết quả theo phạm vi từng creator; không có tìm theo ảnh.
  • 12/08/2026 — observed_commission trên API sản phẩm: rate thực tế (đã gồm HH thưởng) lấy từ đơn gần nhất. commission.rate của TikTok chỉ là rate chuẩn nên tính hoàn tiền theo nó sẽ thấp hơn thực tế.
  • 05/08/2026 — POST /partner/tiktok/affiliate/product-links: tạo link và trả luôn thông tin sản phẩm trong 1 request. Không lấy được thì product = null, link vẫn trả bình thường.
  • 12/07/2026 — API đơn hàng + postback thêm các field hoa hồng thưởng và QC shop. est_commission trước giờ đã gồm thưởng, field mới chỉ tách chi tiết.
Bắt đầu tích hợp

Tài liệu, code mẫu và prompt AI bên dưới xem tự do. Để gọi API thật bạn cần tài khoản RioHub (miễn phí):

1. Đăng ký / đăng nhậpTài khoản RioHub miễn phí.
2. Kết nối creator TikTokAPI chỉ thao tác trên creator mà tài khoản đã kết nối.
3. Tạo API keyNgay tại trang này, dùng ở header X-Riohub-Api-Key.
API Keys của bạn

Key dùng ở header X-Riohub-Api-Key. Mỗi key chỉ thao tác được trên creator TikTok mà chính tài khoản này đã kết nối.

Lưu lại ngay! Key chỉ hiển thị một lần. Sau khi đóng bạn sẽ không xem lại được.
TênPrefixTrạng tháiDữ liệu thị trườngDùng lần cuốiTạo lúc
Đang tải...
Hướng dẫn sử dụng

Base URL:

Tên miền dự phòng
Cả 3 tên miền cùng một hệ thống — API key, đường dẫn, header và định dạng phản hồi giống hệt nhau:
  • https://riohub.vn/api/v1 — chính
  • https://riohub.riokupon.com/api/v1 — dự phòng
  • https://riohub.riokupon.me/api/v1 — dự phòng
Khuyến nghị cấu hình thử lần lượt theo thứ tự trên; chỉ chuyển sang tên miền kế tiếp khi lỗi kết nối / DNS / timeout, không chuyển khi API trả 4xx/5xx.
1. Xác thực

Mọi request gửi kèm header API key:

X-Riohub-Api-Key: rhk_xxxxxxxxxxxxxxxxxxxxxxxx

Lỗi xác thực trả về 401. Gọi creator không thuộc tài khoản của bạn trả về 403.

Giới hạn tần suất: mỗi key tối đa 300 request/phút và 100.000 request/ngày (thành viên RioMCN). Tài khoản mới liên kết TikTok dùng gói dùng thử với hạn mức thấp hơn, hiện ngay trên mục API Keys. Vượt giới hạn trả về 429 kèm header Retry-After.

GET

Lấy thông tin sản phẩm affiliate

API tạo link không kèm thông tin sản phẩm — endpoint này trả tên/ảnh/giá/hoa hồng theo product_id (trong phạm vi quyền của creator). RioHub cache 24h: id đã có trả ngay, id mới/quá hạn mới gọi TikTok.

Query params:

ParamBắt buộcMô tả
creator_usernameCóUsername TikTok creator (đã kết nối).
product_idCó1 id hoặc danh sách phân tách dấu phẩy (tối đa 100).

Phản hồi 200:

{ "creator_username": "your_creator", "requested": 1, "found": 1, "products": [ { "id": "1732152247872817576", "title": "Bộ Cạo Râu 72 Lưỡi + 2 cán Dao Làm và Hộp Nhựa", "main_image_url": "https://p16-oec-sg.ibyteimg.com/.../image", "detail_link": "https://shop.tiktok.com/view/product/1732152247872817576?region=VN", "sale_region": "VN", "has_inventory": true, "units_sold": 62030, "commission": { "rate": 600, "amount": "3299.94 - 5099.94", "currency": "VND" }, "shop_ads_commission": { "rate": 200 }, "sales_price": { "minimum_amount": "", "maximum_amount": "", "currency": "" }, "original_price": { "minimum_amount": "89000", "maximum_amount": "139000", "currency": "VND" }, "shop": { "name": "Nấmm Decor" }, "category_chains": [ { "id": "601450", "is_leaf": false, "local_name": "Chăm sóc sắc đẹp & cá nhân", "parent_id": "0" }, { "id": "700791", "is_leaf": true, "local_name": "Dao cạo", "parent_id": "849288" } ], "observed_commission": { "commission_rate": 2300, "commission_bonus_rate": 1500, "shop_ads_commission_rate": 0, "standard_commission_rate": 800, "scope": "creator", "source": "orders", "last_order_at": "2026-08-10 04:12:33" } } ], "not_found": [] }

Giải thích field trong products[] (passthrough TikTok V202509):

FieldTypeMô tả
idstringMã sản phẩm (int64 dạng chuỗi).
titlestringTên sản phẩm.
main_image_urlstringẢnh chính.
detail_linkstringLink trang sản phẩm trên TikTok Shop.
sale_regionstringKhu vực bán (vd VN).
has_inventorybooltrue = còn hàng.
units_soldnumberTổng số đã bán (lũy kế).
commission.ratenumberTỷ lệ hoa hồng raw, ÷100 = % (vd 600 = 6%).
commission.amountstringHoa hồng ước tính — có thể là 1 khoảng (vd "3299.94 - 5099.94") khi nhiều SKU giá khác nhau.
shop_ads_commission.ratenumberTỷ lệ HH cho đơn Shop Ads (raw, ÷100 = %).
sales_price.{min,max}_amountstringGiá bán hiện tại (khoảng). Có thể rỗng "" nếu TikTok không trả → dùng original_price.
original_price.{min,max}_amountstringGiá gốc (khoảng).
shop.namestringTên shop.
category_chains[]arrayCây ngành hàng (gốc → lá); phần tử is_leaf:true là ngành lá.
observed_commissionobject · vắngField riêng của RioHub (không phải TikTok) — rate thực tế đã gồm HH thưởng, xem bảng dưới. Vắng khi sản phẩm chưa có đơn nào trong dữ liệu RioHub.
commission.rate của TikTok KHÔNG gồm hoa hồng thưởng. Đó là rate chuẩn shop đặt cho public promotion. Hoa hồng thưởng (bonus) gắn với collaboration/campaign nên TikTok không trả ở bất kỳ endpoint sản phẩm nào — chỉ có trên sku của đơn hàng. Muốn hiện hoàn tiền dự kiến đúng, dùng observed_commission.commission_rate; nếu vắng thì fallback commission.rate (+ shop_ads_commission.rate).

Field trong observed_commission (RioHub tính từ đơn gần nhất của sản phẩm):

FieldTypeMô tả
commission_ratenumberRate TỔNG = chuẩn + thưởng + QC shop + reward (raw, ÷100 = %). Dùng field này để tính hoàn tiền.
commission_bonus_ratenumberRate HH thưởng (raw, ÷100 = %).
shop_ads_commission_ratenumberRate HH quảng cáo shop (raw, ÷100 = %).
standard_commission_ratenumberPhần còn lại = tổng − thưởng − QC shop (gồm cả reward rate).
scopestringcreator = lấy từ đơn của chính creator này (chính xác nhất, vì deal thưởng theo từng collaboration) · global = đơn của creator khác trên RioHub (chỉ tham chiếu).
sourcestringLuôn orders — nguồn dữ liệu là đơn affiliate đã sync.
last_order_atstringThời điểm đơn tham chiếu (Y-m-d H:i:s UTC). Rate thưởng theo campaign nên có thể đã hết hạn.

Các field còn lại trong products[] giữ nguyên object từ TikTok. Id không lấy được (không đủ điều kiện / không tồn tại) nằm trong not_found. Dữ liệu cache 24h.

GET

Lấy đơn hàng

Truy vấn đơn affiliate đã đồng bộ theo creator và khoảng thời gian.

Query params:

ParamBắt buộcMô tả
creator_usernameCóCreator thuộc tài khoản của bạn.
time_startKhôngUnix giây hoặc Y-m-d H:i:s — lọc theo ngày tạo (từ).
time_endKhôngUnix giây hoặc Y-m-d H:i:s — lọc theo ngày tạo (đến, không bao gồm).
update_time_startKhôngUnix giây — lọc theo thời điểm đơn được cập nhật (từ). Dùng kéo đơn vừa đổi settlement/refund (sync tăng dần).
update_time_endKhôngUnix giây — lọc theo ngày cập nhật (đến, không bao gồm).
order_idKhông1 mã đơn hoặc danh sách ngăn cách dấu phẩy (tối đa 200) — verify postback / gom 1 đợt. Nên đặt page_size đủ lớn.
product_idKhông1 hoặc list (tối đa 100) — lọc đơn theo sản phẩm.
statusKhông1 hoặc list: 1=pending · 2=settled · 3=cancelled/refunded.
settlement_statusKhông1 hoặc list (passthrough TikTok, vd SETTLED,AWAITING PAYMENT).
content_typeKhông1 hoặc list: VIDEO / LIVE / SHOWCASE / LINKSHARE (passthrough).
live_session_idKhông1 hoặc list (tối đa 100) — chỉ lấy đơn của các ca live này.
fully_refundedKhông0 | 1 — lọc đơn hoàn toàn bộ.
sub_idKhôngKhớp chuỗi con trên tag (vd abc khớp abc-def--).
sub1…sub4KhôngKhớp chính xác từng vị trí của tag (tag tách bằng -); kết hợp nhiều sub = AND.
trace_idKhôngMã click (1 mã hoặc list phân tách dấu phẩy, tối đa 200), khớp chính xác — tra đơn theo event_id bạn gắn vào link /general-links.
page / page_sizeKhôngPhân trang (mặc định 1 / 50, tối đa 200).

Ví dụ (cURL):

Phản hồi 200:

{ "creator_username": "your_creator", "page": 1, "page_size": 50, "total": 12, "orders": [ { "order_id": "579012345678901234", "sku_id": "173098765432109876", "product_id": "172900112233445566", "product_name": "Áo thun cotton unisex", "price": "199000.00", "quantity": 1, "refunded_quantity": 0, "returned_quantity": 0, "fully_refunded": 0, "creator_username": "your_creator", "shop_name": "Rio Official Store", "currency": "VND", "settlement_status": "AWAITING PAYMENT", "status": 1, "tt_order_status": 100, "content_type": "LINKSHARE", "content_id": "744011223344556677", "live_session_id": null, "sub_id": "u27765-m1780903356", "sub1": "u27765", "sub2": "m1780903356", "sub3": "", "sub4": "", "trace_id": "u27765-m1780903356", "trace_type": "SPECIFIC", "commission_model": "Fixed commission", "standard_commission_rate": "800", "commission_rate": 2800, "commission_bonus_rate": 2000, "shop_ads_commission_rate": 0, "commission_gmv": "199000.00", "est_standard_commission": "15920.00", "est_bonus_commission": "39800.00", "est_shop_ads_commission": null, "est_commission": "55720.00", "actual_commission": null, "actual_bonus_commission": null, "actual_shop_ads_commission": null, "create_time": 1749513600, "update_time": 1749520800, "time_created": "2026-06-10 08:00:00", "time_delivered": null, "settled_at": null, "settled_at_iso": null, "time_created_iso": "2026-06-10T08:00:00Z", "time_delivered_iso": null, "settled_at_iso": null, "payment_status": "AWAITING PAYMENT" } ] }

Chi tiết field trong orders[]:

Kiểu JSON: cột số nguyên trả về number; cột tiền/decimal & ID lớn trả về string (giữ nguyên độ chính xác).

FieldTypeGiá trị / EnumMô tả
order_idstring—Mã đơn TikTok (int64 dạng chuỗi).
sku_idstring—Mã SKU. Một đơn nhiều SKU = nhiều dòng (mỗi dòng 1 SKU).
product_idstring—Mã sản phẩm.
product_namestring—Tên sản phẩm.
pricestring—Đơn giá GMV (decimal dạng chuỗi).
quantitynumber—Số lượng đặt.
refunded_quantitynumber—SL đã hoàn tiền.
returned_quantitynumber—SL đã trả hàng.
fully_refundednumber0 · 11 = đơn đã hoàn toàn bộ số lượng.
currencystringVND, USD, …Mã tiền tệ ISO-4217.
settlement_statusstringAWAITING PAYMENT · To-SETTLE · SETTLED · REFUNDEDTrạng thái gốc từ TikTok (passthrough). TikTok có thể bổ sung trạng thái khác.
statusnumber1 · 2 · 3Trạng thái chuẩn hoá: 1=chờ/pending · 2=đã đối soát/settled · 3=huỷ/hoàn.
tt_order_statusnumber100 · 103 · 104Tương ứng status: 100=pending · 103=settled · 104=cancelled/refunded.
content_typestringVIDEO · LIVE · SHOWCASE · LINKSHARE · …Nguồn nội dung phát sinh đơn (passthrough). LINKSHARE = từ link chia sẻ.
content_idstring—ID video / live / showcase nguồn (rỗng nếu không có).
live_session_idstring · null—Mã ca live khi đơn đến từ livestream (content_type = LIVE, bằng content_id); null với nguồn khác. Nối với GET /live-sessions.
sub_idstring—Nhãn theo dõi bạn gắn lúc tạo link (= tag đầy đủ).
sub1…sub4string · null—Tag tách theo - thành 4 vị trí (vd u27765-m178... → sub1=u27765, sub2=m178...). Vị trí trống = ""; đơn không có tag = null. Dùng lọc qua param sub1…sub4.
trace_id · trace_typestring · nullGENERAL · SPECIFICMã click TikTok gắn vào đơn — giống hệt field cùng tên ở postback. GENERAL = link /general-links, trace_id = event_id bạn gắn; SPECIFIC = link /links, trace_id = sub_id. null với đơn cũ chưa được đồng bộ lại.
commission_modelstringpassthrough TikTokMô hình hoa hồng, vd Fixed commission. Giá trị do TikTok định nghĩa.
standard_commission_ratenumberraw, ÷100 = %Tỷ lệ HH chuẩn dạng raw int, vd 1000 = 10%.
commission_ratenumber · nullraw, ÷100 = %Tỷ lệ HH TỔNG (chuẩn + thưởng + QC shop), vd 2800 = 28%. null với đơn cũ chưa backfill.
commission_bonus_ratenumber · nullraw, ÷100 = %Tỷ lệ HH thưởng (bonus), vd 2000 = 20%.
shop_ads_commission_ratenumber · nullraw, ÷100 = %Tỷ lệ HH quảng cáo shop.
commission_gmvstring—GMV dùng để tính hoa hồng (decimal chuỗi).
est_standard_commissionstring—Phần hoa hồng chuẩn ước tính.
est_bonus_commissionstring · null—Phần hoa hồng thưởng ước tính.
est_shop_ads_commissionstring · null—Phần hoa hồng quảng cáo shop ước tính.
est_commissionstring—Hoa hồng ròng ước tính của creator (gồm cả đơn pending, ĐÃ GỒM thưởng + QC shop).
actual_commissionstring · nullnull khi chưa đối soátTiền creator thực nhận sau đối soát. KHÔNG cộng thêm 2 field dưới — TikTok tính actual_commission = chuẩn + thưởng + QC shop + shared_with_partner, trong đó shared_with_partner là số âm (phần chia cho MCN). VD đơn 585510807483352367: 6.080 + 45.600 + 0 − 46.512 = 5.168. Cộng thêm thưởng là cộng ngược lại phần MCN đã giữ ⇒ trả thừa ~10 lần.
actual_standard_commissionstring · nullnull khi chưa đối soátPhần HH chuẩn — gộp trước khi chia MCN, chỉ để hiện breakdown.
actual_bonus_commissionstring · nullnull khi chưa đối soátPhần HH thưởng — gộp trước khi chia MCN, chỉ để hiện breakdown.
shared_with_partnerstring · nullsố ÂM; 0 nếu không chia MCNPhần doanh thu chia cho MCN. Đa số creator = 0.
pitstring · nullsố ÂMThuế TNCN TikTok khấu trừ (10% phần HH chuẩn) — áp cho cả creator thường.
actual_creator_commission_reward_feestring · nullsố ÂM; thường 0Phí reward TikTok khấu trừ.
actual_shop_ads_commissionstring · nullnull khi chưa đối soátPhần HH quảng cáo shop thực nhận — thành phần bên trong actual_commission.
create_timenumberunix giâyThời điểm tạo đơn.
update_timenumberunix giâyLần cập nhật cuối (last_update_time của TikTok) — đổi mỗi lần SKU bị đụng: quyết toán, hoàn, chỉnh rate… Không phải ngày quyết toán.
time_createdstringY-m-d H:i:s (UTC)create_time dạng ngày giờ. Giờ UTC — cộng 7 tiếng ra giờ VN.
time_deliveredstring · nullY-m-d H:i:s (UTC)Thời điểm giao hàng, giờ UTC (null nếu chưa giao).
settled_atstring · nullY-m-d H:i:s (UTC)Ngày quyết toán (thời điểm RioHub ghi nhận đơn chốt hoa hồng — cùng mốc làm badge “Đã chốt HH” bật lên, gồm cả đơn To-SETTLE đã có hoa hồng thực nhận; lệch so với TikTok tối đa bằng nhịp sync ~3h). TikTok không trả mốc này cho creator nên RioHub tự ghi. Đóng dấu khi đơn chuyển sang đã chốt; xoá về null nếu đơn chuyển sang huỷ/hoàn, chốt lại thì nhận mốc mới. null khi: chưa quyết toán · đã quyết toán trước 04/09/2026 · hoặc đơn quyết toán muộn hơn 60 ngày kể từ ngày tạo (cron không quét lại quá mốc đó).
time_created_iso · time_delivered_iso · settled_at_isostring · nullISO 8601, vd 2026-08-29T02:50:22ZNên dùng bộ này. Cùng giá trị với 3 field không hậu tố, nhưng hậu tố Z nói rõ là UTC nên mọi thư viện ngày giờ tự parse đúng — không còn nguy cơ đọc nhầm thành giờ VN.
payment_statusstringnhư settlement_statusTrạng thái thanh toán gốc từ TikTok.

GET

Lấy danh sách ca live

Các ca livestream có đơn của creator, mới nhất trước: số đơn, GMV, hoa hồng, đơn huỷ, giờ lên live và CTV đứng live. Số liệu khớp trang Doanh thu theo ca live. Lấy đơn chi tiết của 1 ca bằng GET /orders?live_session_id=....

Query params:

ParamBắt buộcMô tả
creator_usernameCóCreator thuộc tài khoản của bạn.
time_startKhôngUnix giây hoặc Y-m-d H:i:s (UTC) — lọc theo ngày tạo đơn (từ). Mặc định 30 ngày trước time_end.
time_endKhôngNhư trên (đến, không bao gồm). Mặc định bây giờ. Khoảng tối đa 93 ngày.
live_session_idKhông1 hoặc list (tối đa 100) — chỉ lấy các ca này (trong khoảng thời gian trên).
page / page_sizeKhôngPhân trang (mặc định 1 / 50, tối đa 200).

Ví dụ (cURL):

Phản hồi 200:

{ "creator_username": "your_creator", "time_start": 1790985600, "time_end": 1793577600, "page": 1, "page_size": 50, "total": 18, "truncated": false, "live_sessions": [ { "live_session_id": "7567890123456789012", "creator_username": "your_creator", "live_started_at": 1793541000, "live_started_at_iso": "2026-11-01T13:50:00Z", "first_order_at": 1793542320, "last_order_at": 1793548000, "orders": 12, "cancelled_orders": 2, "items": 15, "gmv": 4500000, "commission": 450000, "settled_commission": 150000, "pending_commission": 300000, "cancelled_gmv": 300000, "host": { "host_id": 7, "name": "Ngọc", "code": "NGOC", "assign_source": "auto", "commission_share_pct": 30, "payout": 135000, "settled_payout": 45000 } } ] }

Chi tiết field trong live_sessions[]:

FieldTypeGiá trị / EnumMô tả
live_session_idstring—Mã ca live (= content_id của đơn LIVE, giống cột “Content ID” khi xuất đơn trên TikTok). Luôn là chuỗi — số 19 chữ số vượt giới hạn số nguyên của JavaScript.
live_started_at · live_started_at_isonumber · nullunix giây · ISO UTCGiờ lên live (giải mã từ mã ca). null nếu không giải mã được — khi đó dùng first_order_at.
first_order_at · last_order_atnumberunix giâyĐơn đầu / đơn cuối của ca (đơn có thể về sau khi tắt live).
orders · itemsnumber—Số đơn và số sản phẩm, không gồm đơn huỷ/hoàn.
cancelled_orders · cancelled_gmvnumber—Đơn huỷ/hoàn (status = 3) và GMV mất, tách riêng.
gmvnumber—Tổng commission_gmv các đơn không huỷ.
commissionnumber—Hoa hồng creator: đơn đã chốt lấy actual_commission, còn lại lấy est_commission.
settled_commission · pending_commissionnumber—Phần đã chốt / phần còn chờ của commission.
hostobject · null—CTV đứng live. null nếu chủ kênh chưa gán ca và chưa khai lịch ca trên RioHub.
host.assign_sourcestringmanual · automanual = chủ kênh gán tay; auto = tự gán theo lịch ca.
host.host_id · host.codenumber · string · null—ID và mã CTV trên RioHub (null với tên nhập tay cũ chưa có trong danh sách CTV).
host.commission_share_pctnumber · null0–100% hoa hồng chia cho CTV.
host.payout · host.settled_payoutnumber · null—Tiền trả CTV = commission (hoặc settled_commission) × %.
truncatedboolean—true khi khoảng thời gian có quá 600 ca — thu hẹp khoảng để lấy đủ.
API dữ liệu thị trường TikTok Shop Cần cấp quyền

Số liệu tổng hợp ~100.000 sản phẩm đang mở affiliate trên TikTok Shop VN và creator bán hàng: số bán 1/7/30 ngày, giá, % hoa hồng, GMV creator, chuỗi theo ngày để vẽ biểu đồ. Cập nhật 1 lần/ngày, lịch sử từ 14/10/2026. Không có đơn hàng hay dữ liệu riêng của creator nào.

1. Tạo API keyỞ mục API Keys phía trên (dùng chung key với API tạo link).
2. Bấm “Xin quyền”Ở cột Dữ liệu thị trường của key, ghi mục đích sử dụng.
3. Chờ RioHub duyệtThường trong ngày làm việc. Trạng thái hiện ngay trên bảng key.
MethodĐường dẫnTrả về
GET/market/tiktok/productsBảng xếp hạng SP. Lọc q, category, shop; sort = sold_7d · sold_30d · sold_1d · sold_total · newest · commission
GET/market/tiktok/products/{product_id}?days=30Chi tiết SP + chuỗi ngày (số bán, luỹ kế, giá, % hoa hồng), days 1–90
GET/market/tiktok/categoriesNgành hàng cấp 1 + số SP (cho bộ lọc)
GET/market/tiktok/creatorsCreator theo GMV (USD, kỳ ~30 ngày). Lọc q, min_followers, category_id; sort = gmv · followers · units_sold · video_gmv · live_gmv
GET/market/tiktok/creators/{username}?days=30Hồ sơ creator + phân bổ follower/ngành + chuỗi ngày

Phân trang: page (1–200), per_page (1–50), xem has_more. Giá VND; trường null = chưa có số (biểu đồ để trống, không vẽ 0). Hiển thị “Số liệu tới ngày stats_date”.

curl -H "X-Riohub-Api-Key: rhk_xxxxxxxx" \ "https://riohub.vn/api/v1/market/tiktok/products?sort=sold_7d&per_page=20"

Key chưa được cấp quyền trả 403 forbidden. Lượt gọi tính chung giới hạn 300/phút, 100.000/ngày của key (429 + Retry-After khi vượt). Dữ liệu đổi 1 lần/ngày ⇒ nên cache 1–3 giờ.

Code mẫu

Sao chép & thay YOUR_API_KEY bằng key của bạn. creator_username tự điền creator đã kết nối của bạn (nếu có).

# 1) Tạo link affiliate curl -X POST '__BASE__/partner/tiktok/affiliate/links' \ -H 'X-Riohub-Api-Key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"creator_username":"__CREATOR__","product_url":"https://vt.tiktok.com/XXXXXX/","sub_id":"fb-ads-01"}' # 1b) Tạo link + lấy luôn thông tin sản phẩm trong 1 request curl -X POST '__BASE__/partner/tiktok/affiliate/product-links' \ -H 'X-Riohub-Api-Key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"creator_username":"__CREATOR__","product_url":"https://vt.tiktok.com/XXXXXX/","sub_id":"fb-ads-01"}' # 1c) Tạo deep link (mở app) theo lô — trả sharing_link + deep_link + one_link, KHÔNG có sub_id curl -X POST '__BASE__/partner/tiktok/affiliate/general-links' \ -H 'X-Riohub-Api-Key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"creator_username":"__CREATOR__","product_ids":["1729...","1730..."]}' # 2) Lấy danh sách link đã tạo curl '__BASE__/partner/tiktok/affiliate/links?creator_username=__CREATOR__&page=1&page_size=50' \ -H 'X-Riohub-Api-Key: YOUR_API_KEY' # 3) Lấy đơn hàng (lọc theo order_id / settlement_status — đều tuỳ chọn) curl '__BASE__/partner/tiktok/affiliate/orders?creator_username=__CREATOR__&order_id=579,580&settlement_status=SETTLED&page=1&page_size=50' \ -H 'X-Riohub-Api-Key: YOUR_API_KEY' # 4) Lấy thông tin sản phẩm affiliate curl '__BASE__/partner/tiktok/affiliate/products?creator_username=__CREATOR__&product_id=1729...,1730...' \ -H 'X-Riohub-Api-Key: YOUR_API_KEY' # 5) Tìm sản phẩm theo từ khoá (chưa biết product_id) — phân trang bằng con trỏ next_page_token curl '__BASE__/partner/tiktok/affiliate/products/search?creator_username=__CREATOR__&keyword=c%E1%BA%A1o%20r%C3%A2u&price_min=30000&rate_min=800&page_size=20' \ -H 'X-Riohub-Api-Key: YOUR_API_KEY' # 6) Theo dõi TỪNG CLICK bằng event_id — dùng sharing_link lấy ở 1c (tạo 1 lần/sản phẩm) # Mỗi click: sinh 1 mã mới, gắn vào CẢ chain_key LẪN cuối link rồi redirect người mua, vd: # https://www.tiktok.com/view/product/1729...?chain_key=%7B%22event_id%22%3A%22c9f2a1b7e4d0%22%2C%22t%22%3A1...&...&event_id=c9f2a1b7e4d0 # deep_link / one_link: thay MỌI chỗ chữ giữ chỗ o_event_id bằng mã click # Đơn về → postback data.trace_type = "GENERAL", data.trace_id = "c9f2a1b7e4d0"
<?php $base = '__BASE__'; $key = 'YOUR_API_KEY'; // 1) Tạo link affiliate $ch = curl_init("$base/partner/tiktok/affiliate/links"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["X-Riohub-Api-Key: $key", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode([ "creator_username" => "__CREATOR__", "product_url" => "https://vt.tiktok.com/XXXXXX/", "sub_id" => "fb-ads-01", ]), ]); $link = json_decode(curl_exec($ch), true); curl_close($ch); echo $link["affiliate_link"] ?? "error"; // 2) Lấy danh sách link đã tạo $q = http_build_query(["creator_username" => "__CREATOR__", "page" => 1, "page_size" => 50]); $ch = curl_init("$base/partner/tiktok/affiliate/links?$q"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["X-Riohub-Api-Key: $key"], ]); $links = json_decode(curl_exec($ch), true); curl_close($ch); print_r($links["links"] ?? []); // 3) Lấy đơn hàng (tất cả filter đều tuỳ chọn) $q = http_build_query([ "creator_username" => "__CREATOR__", "order_id" => "584428814795835227,583754926547633737", // 1 hoặc list (verify postback) "status" => "1,2", // 1=pending 2=settled 3=cancelled "settlement_status" => "SETTLED", "update_time_start" => 1778000000, // chỉ đơn cập nhật sau mốc này (sync tăng dần) "sub1" => "u27765", // khớp vị trí 1 của tag "page" => 1, "page_size" => 200, ]); $ch = curl_init("$base/partner/tiktok/affiliate/orders?$q"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["X-Riohub-Api-Key: $key"], ]); $orders = json_decode(curl_exec($ch), true); curl_close($ch); print_r($orders["orders"] ?? []); // 4) Lấy thông tin sản phẩm affiliate (1 hoặc nhiều product_id) $q = http_build_query(["creator_username" => "__CREATOR__", "product_id" => "1732152247872817576"]); $ch = curl_init("$base/partner/tiktok/affiliate/products?$q"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["X-Riohub-Api-Key: $key"], ]); $products = json_decode(curl_exec($ch), true); curl_close($ch); print_r($products["products"] ?? []); // 6) Theo dõi TỪNG CLICK bằng event_id — link general (KHÔNG có sub_id) // Bước 1 (1 lần/sản phẩm): tạo link gốc rồi lưu lại, KHÔNG gọi API mỗi click $ch = curl_init("$base/partner/tiktok/affiliate/general-links"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["X-Riohub-Api-Key: $key", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode([ "creator_username" => "__CREATOR__", "product_ids" => ["1729601735665682699"], ]), ]); $gen = json_decode(curl_exec($ch), true); curl_close($ch); $sharingLink = $gen["links"][0]["sharing_link"] ?? ""; // Bước 2 (mỗi click): sinh mã duy nhất, lưu (mã → nguồn click) rồi redirect người mua function withClickId(string $url, string $id): string { if (!preg_match('/^[A-Za-z0-9._-]+$/', $id)) throw new InvalidArgumentException('click id chỉ gồm [A-Za-z0-9._-]'); // deep_link / one_link: TikTok để sẵn chữ giữ chỗ "o_event_id" (trong chain_key + event_id) → thay hết if (str_contains($url, 'o_event_id')) return str_replace('o_event_id', $id, $url); // sharing_link: gắn vào chain_key (giống link TikTok tự sinh) + nối event_id cuối link if (str_contains($url, 'chain_key=%7B') && !str_contains($url, '%22event_id%22')) { $url = preg_replace('/chain_key=%7B/', 'chain_key=%7B%22event_id%22%3A%22' . $id . '%22%2C', $url, 1); } return $url . (str_contains($url, '?') ? '&' : '?') . 'event_id=' . $id; } $clickId = 'c' . bin2hex(random_bytes(12)); // chỉ dùng [A-Za-z0-9._-] $target = withClickId($sharingLink, $clickId); echo $target; // trang redirect thật: header("Location: $target", true, 302); // Bước 3: đơn từ click này → postback data.trace_type = "GENERAL", data.trace_id = $clickId
const BASE = '__BASE__'; const KEY = 'YOUR_API_KEY'; // 1) Tạo link affiliate const link = await fetch(`${BASE}/partner/tiktok/affiliate/links`, { method: 'POST', headers: { 'X-Riohub-Api-Key': KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ creator_username: '__CREATOR__', product_url: 'https://vt.tiktok.com/XXXXXX/', sub_id: 'fb-ads-01', }), }).then(r => r.json()); console.log(link.affiliate_link); // 2) Lấy danh sách link đã tạo const lq = new URLSearchParams({ creator_username: '__CREATOR__', page: '1', page_size: '50' }); const links = await fetch(`${BASE}/partner/tiktok/affiliate/links?${lq}`, { headers: { 'X-Riohub-Api-Key': KEY }, }).then(r => r.json()); console.log(links.total, links.links); // 3) Lấy đơn hàng (tất cả filter đều tuỳ chọn) const q = new URLSearchParams({ creator_username: '__CREATOR__', order_id: '584428814795835227,583754926547633737', // 1 hoặc list (verify postback) status: '1,2', // 1=pending 2=settled 3=cancelled settlement_status: 'SETTLED', update_time_start: '1778000000', // chỉ đơn cập nhật sau mốc này (sync tăng dần) sub1: 'u27765', // khớp vị trí 1 của tag page: '1', page_size: '200', }); const orders = await fetch(`${BASE}/partner/tiktok/affiliate/orders?${q}`, { headers: { 'X-Riohub-Api-Key': KEY }, }).then(r => r.json()); console.log(orders.total, orders.orders); // 4) Lấy thông tin sản phẩm affiliate (1 hoặc nhiều product_id) const pq = new URLSearchParams({ creator_username: '__CREATOR__', product_id: '1732152247872817576' }); const products = await fetch(`${BASE}/partner/tiktok/affiliate/products?${pq}`, { headers: { 'X-Riohub-Api-Key': KEY }, }).then(r => r.json()); console.log(products.found, products.products); // 5) Tìm sản phẩm theo từ khoá — duyệt hết trang bằng con trỏ next_page_token let pageToken = '', all = []; do { const sq = new URLSearchParams({ creator_username: '__CREATOR__', keyword: 'cạo râu', rate_min: '800', // hoa hồng thô: 800 = 8% page_size: '20', }); if (pageToken) sq.set('page_token', pageToken); const page = await fetch(`${BASE}/partner/tiktok/affiliate/products/search?${sq}`, { headers: { 'X-Riohub-Api-Key': KEY }, }).then(r => r.json()); all.push(...(page.products || [])); pageToken = page.next_page_token || ''; // rỗng = hết trang } while (pageToken && all.length < 100); // chặn trên cho ví dụ console.log(all.length, all[0]?.id, all[0]?.title); // 6) Theo dõi TỪNG CLICK bằng event_id — link general (KHÔNG có sub_id) // Bước 1 (1 lần/sản phẩm): tạo link gốc rồi lưu lại, KHÔNG gọi API mỗi click const gen = await fetch(`${BASE}/partner/tiktok/affiliate/general-links`, { method: 'POST', headers: { 'X-Riohub-Api-Key': KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ creator_username: '__CREATOR__', product_ids: ['1729601735665682699'] }), }).then(r => r.json()); const sharingLink = gen.links?.[0]?.sharing_link || ''; // Bước 2 (mỗi click): sinh mã duy nhất, lưu (mã → nguồn click) rồi redirect người mua const withClickId = (url, id) => { if (!/^[A-Za-z0-9._-]+$/.test(id)) throw new Error('click id chỉ gồm [A-Za-z0-9._-]'); // deep_link / one_link: TikTok để sẵn chữ giữ chỗ "o_event_id" (trong chain_key + event_id) → thay hết if (url.includes('o_event_id')) return url.split('o_event_id').join(id); // sharing_link: gắn vào chain_key (giống link TikTok tự sinh) + nối event_id cuối link if (url.includes('chain_key=%7B') && !url.includes('%22event_id%22')) url = url.replace('chain_key=%7B', 'chain_key=%7B%22event_id%22%3A%22' + id + '%22%2C'); return url + (url.includes('?') ? '&' : '?') + 'event_id=' + id; }; const clickId = 'c' + crypto.randomUUID().replace(/-/g, ''); // chỉ dùng [A-Za-z0-9._-] console.log(withClickId(sharingLink, clickId)); // Express: res.redirect(302, ...) // Bước 3: đơn từ click này → postback data.trace_type = 'GENERAL', data.trace_id = clickId
import requests BASE = "__BASE__" KEY = "YOUR_API_KEY" H = {"X-Riohub-Api-Key": KEY} # 1) Tạo link affiliate link = requests.post(f"{BASE}/partner/tiktok/affiliate/links", headers=H, json={ "creator_username": "__CREATOR__", "product_url": "https://vt.tiktok.com/XXXXXX/", "sub_id": "fb-ads-01", }).json() print(link.get("affiliate_link")) # 2) Lấy danh sách link đã tạo links = requests.get(f"{BASE}/partner/tiktok/affiliate/links", headers=H, params={ "creator_username": "__CREATOR__", "page": 1, "page_size": 50, }).json() print(links.get("total"), links.get("links")) # 3) Lấy đơn hàng (tất cả filter đều tuỳ chọn) orders = requests.get(f"{BASE}/partner/tiktok/affiliate/orders", headers=H, params={ "creator_username": "__CREATOR__", "order_id": "584428814795835227,583754926547633737", # 1 hoặc list (verify postback) "status": "1,2", # 1=pending 2=settled 3=cancelled "settlement_status": "SETTLED", "update_time_start": 1778000000, # chỉ đơn cập nhật sau mốc này (sync tăng dần) "sub1": "u27765", # khớp vị trí 1 của tag "page": 1, "page_size": 200, }).json() print(orders.get("total"), orders.get("orders")) # 4) Lấy thông tin sản phẩm affiliate (1 hoặc nhiều product_id) products = requests.get(f"{BASE}/partner/tiktok/affiliate/products", headers=H, params={ "creator_username": "__CREATOR__", "product_id": "1732152247872817576", }).json() print(products.get("found"), products.get("products")) # 5) Tìm sản phẩm theo từ khoá — duyệt hết trang bằng con trỏ next_page_token page_token, found = "", [] while True: params = { "creator_username": "__CREATOR__", "keyword": "cạo râu", "rate_min": 800, # hoa hồng thô: 800 = 8% "page_size": 20, } if page_token: params["page_token"] = page_token page = requests.get(f"{BASE}/partner/tiktok/affiliate/products/search", headers=H, params=params).json() found += page.get("products") or [] page_token = page.get("next_page_token") or "" # rỗng = hết trang if not page_token or len(found) >= 100: break print(len(found), found[0].get("id") if found else None) # 6) Theo dõi TỪNG CLICK bằng event_id — link general (KHÔNG có sub_id) # Bước 1 (1 lần/sản phẩm): tạo link gốc rồi lưu lại, KHÔNG gọi API mỗi click import re, uuid gen = requests.post(f"{BASE}/partner/tiktok/affiliate/general-links", headers=H, json={ "creator_username": "__CREATOR__", "product_ids": ["1729601735665682699"], }).json() sharing_link = (gen.get("links") or [{}])[0].get("sharing_link", "") # Bước 2 (mỗi click): sinh mã duy nhất, lưu (mã → nguồn click) rồi redirect người mua def with_click_id(url, click_id): if not re.fullmatch(r"[A-Za-z0-9._-]+", click_id): raise ValueError("click id chỉ gồm [A-Za-z0-9._-]") # deep_link / one_link: TikTok để sẵn chữ giữ chỗ "o_event_id" (trong chain_key + event_id) → thay hết if "o_event_id" in url: return url.replace("o_event_id", click_id) # sharing_link: gắn vào chain_key (giống link TikTok tự sinh) + nối event_id cuối link if "chain_key=%7B" in url and "%22event_id%22" not in url: url = url.replace("chain_key=%7B", "chain_key=%7B%22event_id%22%3A%22" + click_id + "%22%2C", 1) return url + ("&" if "?" in url else "?") + "event_id=" + click_id click_id = "c" + uuid.uuid4().hex # chỉ dùng [A-Za-z0-9._-] print(with_click_id(sharing_link, click_id)) # Flask: return redirect(..., 302) # Bước 3: đơn từ click này → postback data["trace_type"] == "GENERAL", data["trace_id"] == click_id
Tích hợp bằng AI

Chọn việc cần làm + ngôn ngữ → trang tự soạn prompt đầy đủ (spec API + yêu cầu cụ thể). Dán vào ChatGPT / Claude / Copilot / Cursor là AI viết được code chạy được. Không chứa key thật: giữ YOUR_API_KEY, khi chạy đọc key từ biến môi trường.

1. Bạn muốn làm gì? (chọn nhiều)
2. Ngôn ngữ / nền tảng
3. Mô tả thêm (tuỳ chọn)
4. Gửi cho AI
Đã copy — dán vào khung chat (Ctrl/⌘+V) rồi gửi.
Link spec cho AI agent (công khai, luôn bản mới): https://riohub.vn/tiktok-shop-developer/ai-spec
  • Agent đọc được URL (Claude Code, Cursor @Docs, ChatGPT có duyệt web): đưa link spec ở trên kèm yêu cầu — không cần copy cả khối.
  • Cursor / Copilot / Claude Code: tải file .md, đặt vào repo (vd docs/riohub-api.md) rồi nhắc AI “làm theo docs/riohub-api.md”.
  • Spec tự cập nhật theo tài liệu trên trang này — khi API có thay đổi, copy lại bản mới.
  • Chạy thử code AI viết ở mục Thử nghiệm trước, sai field thì dán phản hồi lỗi cho AI sửa.
# RioHub × TikTok Shop Affiliate API — Spec tích hợp > Tài liệu cập nhật: 02/11/2026 Bạn là kỹ sư tích hợp. Hãy viết code gọi các API dưới đây theo ngôn ngữ tôi chỉ định. Đọc API key từ biến môi trường (KHÔNG hardcode, KHÔNG để lộ key); thay `YOUR_API_KEY` bằng key thật khi chạy. ## Base URL `__BASE__` Tên miền dự phòng (cùng hệ thống, API key/đường dẫn/phản hồi giống hệt): `https://riohub.vn/api/v1` → `https://riohub.riokupon.com/api/v1` → `https://riohub.riokupon.me/api/v1` Hãy cấu hình danh sách base URL này, thử lần lượt: chỉ chuyển sang tên miền kế tiếp khi lỗi kết nối / DNS / timeout — KHÔNG chuyển khi API trả 4xx/5xx. ## Xác thực Mọi request kèm header: `X-Riohub-Api-Key: YOUR_API_KEY` - `401` key sai/thiếu · `403` creator không thuộc tài khoản của key · `404` creator chưa kết nối - `429` vượt giới hạn (300 req/phút, 100.000 req/ngày với thành viên RioMCN; gói dùng thử thấp hơn; riêng `/links/batch` mỗi sub_id tính là 1) — đọc header `Retry-After` rồi thử lại. ## 1) POST `/partner/tiktok/affiliate/links` — Tạo link affiliate Body JSON: - `creator_username` (bắt buộc): username creator đã kết nối, vd `__CREATOR__` - `product_url` (bắt buộc*): URL sản phẩm (link rút gọn vt.tiktok.com / link trực tiếp) hoặc `product_id`. (*hoặc gửi `product_id`) - `sub_id` (bắt buộc): nhãn theo dõi, 1–128 ký tự `[A-Za-z0-9_-]`. Có thể gói tối đa 4 sub theo vị trí, phân tách bằng `-` (vd `abc-def--` → sub1=abc, sub2=def, sub3/4=rỗng); RioHub tự tách thành `sub1..sub4` để lọc đơn theo từng vị trí. Lưu ý: giá trị mỗi sub KHÔNG được chứa `-` (đó là dấu phân tách). - `type` (tuỳ chọn): mặc định `PRODUCT` - `channel` (tuỳ chọn): nhãn nguồn traffic, mặc định `riokupon`. 1–64 ký tự `[A-Za-z0-9_-]` Request: { "creator_username": "__CREATOR__", "product_url": "https://vt.tiktok.com/XXXXXX/", "sub_id": "fb-ads-01" } Response 200: { "affiliate_link": "https://vt.tiktok.com/XXXXXX/", "sub_id": "fb-ads-01", "product_id": "1729...", "creator_username": "__CREATOR__" } Lỗi 422 `product_not_promotable`: sản phẩm không có hoa hồng, chưa được shop duyệt, hoặc bị TikTok hạn chế (xem `message`). ## 1a) POST `/partner/tiktok/affiliate/links/batch` — Tạo NHIỀU link cho 1 sản phẩm, mỗi sub_id 1 link Dùng khi cần nhiều link cho cùng sản phẩm (vd mỗi người dùng 1 sub_id riêng) — nhanh hơn nhiều so với gọi `/links` từng cái. Body JSON: giống `/links` nhưng thay `sub_id` bằng `sub_ids`: mảng (hoặc chuỗi phân tách dấu phẩy) tối đa 50 giá trị, mỗi giá trị cùng quy tắc như `sub_id`. Trùng lặp được gộp. Request: { "creator_username": "__CREATOR__", "product_id": "1729...", "sub_ids": ["u1001", "u1002", "u1003"] } Response 200 (thành công một phần là bình thường — gửi lại các sub_id trong `failed`): { "creator_username": "__CREATOR__", "product_id": "1729...", "links": [ { "sub_id": "u1001", "affiliate_link": "https://www.tiktok.com/t/..." } ], "failed": [ { "sub_id": "u1003", "error": "..." } ] } Không tạo được link nào → trả lỗi giống `/links` (422 `product_not_promotable` / 502 `tiktok_shop_error`). ## 1b) POST `/partner/tiktok/affiliate/product-links` — Tạo link + thông tin sản phẩm (1 request) Body giống hệt `/links` (`creator_username`, `product_url`|`product_id`, `sub_id`, `type?`, `channel?`). Khác biệt: response kèm object `product` (tên, ảnh, giá, hoa hồng) — không cần gọi thêm `GET /products`. Response 200: { "affiliate_link": "https://vt.tiktok.com/XXXXXX/", "sub_id": "fb-ads-01", "product_id": "1729...", "creator_username": "__CREATOR__", "product": { "id": "1729...", "title": "...", "main_image_url": "https://...", "commission": { "rate": 600, "amount": "3299.94", "currency": "VND" }, "original_price": { "minimum_amount": "89000", "maximum_amount": "139000", "currency": "VND" } }, "product_error": null } Lấy thông tin sản phẩm là best-effort: lỗi → `product` = null, `product_error` ghi lý do, link vẫn trả về. ## 1c) POST `/partner/tiktok/affiliate/general-links` — Tạo deep link (mở app), theo lô Tạo link cho nhiều sản phẩm cùng lúc (tối đa 50). Mỗi sản phẩm trả 3 link: `sharing_link` (web), `deep_link` (mở app TikTok), `one_link` (AppsFlyer, tự chuyển store nếu chưa cài app). LƯU Ý: link này KHÔNG mang `sub_id` — không gắn công theo sub_id như `/links`. Dùng khi cần link mở app để chia sẻ; cần theo dõi hoa hồng theo sub_id thì dùng `/links`. Body JSON: - `creator_username` (bắt buộc): username creator đã kết nối, vd `__CREATOR__` - `product_ids` (bắt buộc*): mảng product_id, tối đa 50. (*hoặc `product_id` dạng chuỗi phân tách dấu phẩy) - `campaign_id` (tuỳ chọn): id chiến dịch affiliate nếu sản phẩm thuộc campaign - `link_type` (tuỳ chọn): rỗng = URL TikTok Shop; `TOKO` = URL Tokopedia Request: { "creator_username": "__CREATOR__", "product_ids": ["1729...", "1730..."] } Response 200: { "creator_username": "__CREATOR__", "links": [ { "material_id": "1729...", "sharing_link": "https://www.tiktok.com/view/product/1729...?...", "deep_link": "snssdk1180://ec/pdp?...", "one_link": "https://snssdk1180.onelink.me/BAuo?..." } ], "failed": [ { "material_id": "1730...", "fail_reason": "Product was sold out" } ] } Không tạo được link nào → `502`. Theo dõi từng click bằng `event_id` (thay cho sub_id): bạn tự sinh 1 mã duy nhất cho mỗi click, rồi gắn vào link TRƯỚC khi chuyển hướng người mua: - `deep_link` / `one_link`: TikTok đã để sẵn chữ giữ chỗ `o_event_id` (là GIÁ TRỊ, nằm trong cả `chain_key` lẫn tham số `event_id`, có thể bị encode nhiều lớp) → thay MỌI chỗ chữ `o_event_id` bằng `<mã>`. - `sharing_link`: không có chữ giữ chỗ → gắn ở 2 chỗ giống link TikTok tự sinh: chèn `"event_id":"<mã>"` vào đầu JSON của `chain_key` (dạng encode: `chain_key=%7B` → `chain_key=%7B%22event_id%22%3A%22<mã>%22%2C`), và nối `&event_id=<mã>` vào cuối URL. - Tạo link gốc 1 lần cho mỗi sản phẩm rồi lưu lại; mỗi click chỉ sinh mã mới và gắn vào, KHÔNG gọi API mỗi click. Hàm gắn mã (JS): ```js const withClickId = (url, id) => { if (!/^[A-Za-z0-9._-]+$/.test(id)) throw new Error('click id chỉ gồm [A-Za-z0-9._-]'); // deep_link / one_link: TikTok để sẵn chữ giữ chỗ "o_event_id" (trong chain_key + event_id) → thay hết if (url.includes('o_event_id')) return url.split('o_event_id').join(id); // sharing_link: gắn vào chain_key (giống link TikTok tự sinh) + nối event_id cuối link if (url.includes('chain_key=%7B') && !url.includes('%22event_id%22')) url = url.replace('chain_key=%7B', 'chain_key=%7B%22event_id%22%3A%22' + id + '%22%2C'); return url + (url.includes('?') ? '&' : '?') + 'event_id=' + id; }; ``` Không encode/decode lại phần còn lại của link. Mã BẮT BUỘC chỉ gồm `[A-Za-z0-9._-]` (để khỏi phải encode qua nhiều lớp). Đơn phát sinh từ link đó sẽ mang lại mã này ở field `trace_id` (với `trace_type` = `GENERAL`) trong postback. ## 2) GET `/partner/tiktok/affiliate/links` — Lấy danh sách link đã tạo Liệt kê link đã tạo qua API (source=`api`) cho creator, kèm số đơn & hoa hồng theo `sub_id`. Query params: - `creator_username` (bắt buộc) - `sub_id` (tuỳ chọn): khớp **chuỗi con** trên tag; `channel` (tuỳ chọn): lọc nguồn traffic - `sub1`, `sub2`, `sub3`, `sub4` (tuỳ chọn): lọc khớp chính xác theo từng vị trí của tag - `time_start`, `time_end` (tuỳ chọn): unix giây hoặc `Y-m-d H:i:s` (lọc theo ngày tạo, end không bao gồm) - `page` (mặc định 1), `page_size` (mặc định 50, tối đa 200) Response 200: { "creator_username": "__CREATOR__", "page": 1, "page_size": 50, "total": 8, "links": [ { "id": 42, "material_id": "1729...", "affiliate_link": "https://vt.tiktok.com/XXXXXX/", "channel": "riokupon", "sub_id": "fb-ads-01", "order_count": 3, "est_commission": "37.50", "settled_commission": "12.50", "currency": "VND", "created_at": "..." } ] } ## 3) GET `/partner/tiktok/affiliate/orders` — Lấy đơn hàng Query params: - `creator_username` (bắt buộc) - `time_start`, `time_end` (tuỳ chọn): unix giây hoặc `Y-m-d H:i:s` (lọc từ / đến, end không bao gồm) - `sub_id` (tuỳ chọn): khớp **chuỗi con** trên tag đầy đủ (vd `abc` khớp `abc-def---`) - `sub1`, `sub2`, `sub3`, `sub4` (tuỳ chọn): lọc **khớp chính xác** theo từng vị trí của tag (tag tách bằng `-`); kết hợp nhiều sub = AND - `order_id` (tuỳ chọn): 1 mã đơn hoặc danh sách phân tách bằng dấu phẩy (tối đa 200) — dùng để verify postback hoặc gom 1 đợt (vd hold postback ~5p rồi lấy 1 loạt). Nên đặt `page_size` đủ lớn. - `settlement_status` (tuỳ chọn): 1 giá trị hoặc danh sách phân tách bằng dấu phẩy (vd `SETTLED,AWAITING PAYMENT`) - `status` (tuỳ chọn): trạng thái chuẩn hoá, 1 giá trị hoặc list (`1`=pending · `2`=settled · `3`=cancelled/refunded) - `update_time_start`, `update_time_end` (tuỳ chọn): unix giây, lọc theo thời điểm đơn **được cập nhật** (end không bao gồm) — dùng để kéo đơn vừa đổi settlement/refund (sync tăng dần) - `product_id` (tuỳ chọn): 1 hoặc list (tối đa 100) — lọc đơn theo sản phẩm - `content_type` (tuỳ chọn): 1 hoặc list, theo nguồn nội dung (VIDEO/LIVE/SHOWCASE/LINKSHARE — passthrough) - `live_session_id` (tuỳ chọn): 1 hoặc list (tối đa 100) — chỉ lấy đơn của các ca live này (mã lấy từ mục 3b) - `fully_refunded` (tuỳ chọn): `0` | `1` - `trace_id` (tuỳ chọn): mã click, 1 hoặc list (tối đa 200), khớp chính xác — tra đơn theo `event_id` đã gắn vào link `/general-links` - `page` (mặc định 1), `page_size` (mặc định 50, tối đa 200) Response 200: { "creator_username": "__CREATOR__", "page": 1, "page_size": 50, "total": 12, "orders": [ { "order_id": "...", "sku_id": "...", "product_id": "...", "product_name": "...", "sub_id": "abc-def--", "sub1": "abc", "sub2": "def", "sub3": "", "sub4": "", "trace_id": "abc-def--", "trace_type": "SPECIFIC", "quantity": 1, "refunded_quantity": 0, "fully_refunded": 0, "est_commission": "12.50", "actual_commission": null, "currency": "VND", "settlement_status": "AWAITING PAYMENT", "status": 1, "tt_order_status": 100, "content_type": "VIDEO", "create_time": 1749513600 } ] } Kiểu JSON: cột số nguyên = number, cột tiền/decimal & ID lớn = string. **Múi giờ:** `create_time` / `update_time` là unix giây (không phụ thuộc múi giờ). Mọi field chuỗi ngày giờ — `time_created`, `time_delivered`, `settled_at` — đều là **UTC**, cộng 7 tiếng ra giờ Việt Nam. **Khuyến nghị:** dùng `create_time` / `update_time` (unix giây) hoặc bộ `*_iso` (`time_created_iso`, `time_delivered_iso`, `settled_at_iso`, và `created_at_iso` ở endpoint link). Hai dạng này không thể hiểu sai múi giờ. Các field chuỗi trần vẫn giữ nguyên để không phá client cũ, nhưng chúng không mang dấu hiệu múi giờ nên dễ bị đọc nhầm thành giờ VN. Enum các field trạng thái trong orders[]: - `status` (number): 1=pending · 2=settled · 3=cancelled/refunded - `tt_order_status` (number): 100=pending · 103=settled · 104=cancelled/refunded - `settlement_status` / `payment_status` (string, passthrough TikTok): AWAITING PAYMENT · To-SETTLE · SETTLED · REFUNDED - `fully_refunded` (number): 0 | 1 - `trace_type` (string · null): GENERAL (link `/general-links`, `trace_id` = event_id bạn gắn) · SPECIFIC (link `/links`, `trace_id` = sub_id) · null (đơn cũ chưa đồng bộ lại) - `content_type` (string, passthrough): VIDEO · LIVE · SHOWCASE · LINKSHARE · … - `live_session_id` (string|null): mã ca live khi `content_type` = LIVE (bằng `content_id`), null với nguồn khác — nối với mục 3b - `actual_commission` (string|null): tiền creator THỰC NHẬN (null khi chưa đối soát). Công thức TikTok: `actual_standard_commission + actual_bonus_commission + actual_shop_ads_commission + shared_with_partner + pit + actual_creator_commission_reward_fee` (3 field cuối là các khoản TRỪ, đã mang dấu ÂM: chia MCN, thuế TNCN 10%, phí reward). KHÔNG cộng thêm 6 field đó vào — chúng chỉ để hiện breakdown - `sub1`..`sub4` (string): tag tách theo `-` (vị trí trống = `""`) - `settled_at` (string|null, `Y-m-d H:i:s` UTC): ngày quyết toán — thời điểm RioHub **ghi nhận** đơn chốt hoa hồng (`status` = 2, cùng mốc badge “Đã chốt HH” — gồm cả đơn `To-SETTLE` đã có hoa hồng thực nhận; lệch so với TikTok tối đa bằng nhịp sync ~3h). Đóng dấu khi đơn chuyển sang đã chốt; **xoá về `null` nếu đơn chuyển sang huỷ/hoàn**, chốt lại thì nhận mốc mới. `update_time` KHÔNG phải mốc này — đó là `last_update_time` của TikTok, đổi mỗi lần SKU bị đụng. `null` khi: chưa quyết toán · quyết toán trước 04/09/2026 · hoặc quyết toán muộn hơn 60 ngày kể từ ngày tạo đơn (cron không quét lại quá mốc đó) ## 3b) GET `/partner/tiktok/affiliate/live-sessions` — Danh sách ca live Query params: - `creator_username` (bắt buộc) - `time_start`, `time_end` (tuỳ chọn): unix giây hoặc `Y-m-d H:i:s` UTC, lọc theo ngày tạo đơn (end không bao gồm). Mặc định 30 ngày gần nhất, tối đa 93 ngày - `live_session_id` (tuỳ chọn): 1 hoặc list (tối đa 100) - `page` (mặc định 1), `page_size` (mặc định 50, tối đa 200) Response 200 (mới nhất trước): { "creator_username": "__CREATOR__", "time_start": 1790985600, "time_end": 1793577600, "page": 1, "page_size": 50, "total": 18, "truncated": false, "live_sessions": [ { "live_session_id": "7567890123456789012", "creator_username": "__CREATOR__", "live_started_at": 1793541000, "live_started_at_iso": "2026-11-01T13:50:00Z", "first_order_at": 1793542320, "last_order_at": 1793548000, "orders": 12, "cancelled_orders": 2, "items": 15, "gmv": 4500000, "commission": 450000, "settled_commission": 150000, "pending_commission": 300000, "cancelled_gmv": 300000, "host": { "host_id": 7, "name": "Ngọc", "code": "NGOC", "assign_source": "auto", "commission_share_pct": 30, "payout": 135000, "settled_payout": 45000 } } ] } Ghi chú: `live_session_id` luôn là CHUỖI (19 chữ số, vượt số nguyên an toàn của JS). Số tiền ở đây là number (đã cộng). `orders`/`items`/`gmv`/`commission` KHÔNG gồm đơn huỷ/hoàn (`cancelled_*` tách riêng). `commission` = actual với đơn đã chốt, est với đơn còn lại. `live_started_at` null ⇒ dùng `first_order_at`. `host` null = chủ kênh chưa gán CTV; `assign_source` manual (gán tay) | auto (theo lịch ca). `truncated` true ⇒ khoảng có hơn 600 ca, thu hẹp lại. Đơn chi tiết của 1 ca: `GET /orders?live_session_id=<mã>`. ## 4) GET `/partner/tiktok/affiliate/products` — Lấy thông tin sản phẩm affiliate API tạo link không kèm thông tin sản phẩm — dùng endpoint này lấy tên/ảnh/giá/hoa hồng theo `product_id` (trong phạm vi quyền của creator). RioHub cache thông tin sản phẩm 24h: id đã có trong cache trả về ngay, id mới/quá 24h mới gọi TikTok → giảm tải, nhanh hơn. Query params: - `creator_username` (bắt buộc) - `product_id` (bắt buộc): 1 id hoặc danh sách phân tách bằng dấu phẩy (tối đa 100) Response 200 (`products[]` = object passthrough TikTok V202509, snake_case): { "creator_username": "__CREATOR__", "requested": 1, "found": 1, "products": [ { "id": "1732...", "title": "...", "main_image_url": "https://...", "detail_link": "https://shop.tiktok.com/view/product/1732...", "sale_region": "VN", "has_inventory": true, "units_sold": 62030, "commission": { "rate": 600, "amount": "3299.94 - 5099.94", "currency": "VND" }, "shop_ads_commission": { "rate": 200 }, "sales_price": { "minimum_amount": "", "maximum_amount": "", "currency": "" }, "original_price": { "minimum_amount": "89000", "maximum_amount": "139000", "currency": "VND" }, "shop": { "name": "..." }, "category_chains": [ { "id": "700791", "is_leaf": true, "local_name": "Dao cạo", "parent_id": "849288" } ], "observed_commission": { "commission_rate": 2300, "commission_bonus_rate": 1500, "shop_ads_commission_rate": 0, "standard_commission_rate": 800, "scope": "creator", "source": "orders", "last_order_at": "2026-08-10 04:12:33" } } ], "not_found": [] } QUAN TRỌNG khi hiện hoàn tiền dự kiến: `commission.rate` của TikTok chỉ là rate CHUẨN, KHÔNG gồm hoa hồng thưởng (bonus gắn với collaboration/campaign, TikTok không trả ở endpoint sản phẩm). Dùng `observed_commission.commission_rate` (rate TỔNG thực tế, RioHub lấy từ đơn gần nhất của sản phẩm; `scope`=creator là đơn của chính creator, `global` là tham chiếu từ creator khác); nếu không có key `observed_commission` thì fallback `commission.rate` + `shop_ads_commission.rate`. Ghi chú field: `commission.rate` raw ÷100 = % (600 = 6%); `commission.amount` có thể là khoảng "min - max"; `sales_price` có thể rỗng → dùng `original_price`; `units_sold` lũy kế; `has_inventory` còn hàng; ngành lá = phần tử `is_leaf:true`. Id không lấy được (không đủ điều kiện / không tồn tại) nằm trong `not_found`. Dữ liệu cache 24h. ## 4b) GET `/partner/tiktok/affiliate/products/search` — Tìm sản phẩm theo TỪ KHOÁ Dùng khi chỉ biết TÊN sản phẩm, chưa có `product_id`/`product_url` (đối chiếu sản phẩm giữa các sàn, duyệt catalogue). Có `id` rồi thì gọi `POST /product-links` để tạo link. RioHub cache 1 giờ theo (creator + bộ filter). Query params: - `creator_username` (bắt buộc) - `keyword` (tuỳ chọn): khớp theo TÊN sản phẩm, tối đa 200 ký tự. Bỏ trống = danh sách mặc định - `price_min`/`price_max` (tuỳ chọn): khoảng giá VND, một đầu cũng hợp lệ - `rate_min`/`rate_max` (tuỳ chọn): khoảng hoa hồng, số THÔ — 800 = 8% - `sold_min`/`sold_max` (tuỳ chọn): khoảng lượt bán lũy kế - `rating_min`/`rating_max` (tuỳ chọn): khoảng điểm shop 0–5 - `category_ids` (tuỳ chọn): CSV, tối đa 20 - `sort_type` (tuỳ chọn): chuyển thẳng lên TikTok, họ chưa công bố tập giá trị hợp lệ - `page_size` (tuỳ chọn): 1-50, mặc định 20 - `page_token` (tuỳ chọn): CON TRỎ lấy từ `next_page_token` của phản hồi trước — KHÔNG phải số trang Response 200: { "creator_username": "__CREATOR__", "keyword": "cạo râu", "page_size": 20, "count": 2, "products": [ { "id": "1732...", "title": "...", "main_image_url": "https://...", "price": { "amount": "89000", "currency": "VND" }, "commission": { "rate": 600 }, "seller_name": "...", "observed_commission": { "commission_rate": 2300, "commission_bonus_rate": 1500, "scope": "creator" } } ], "next_page_token": "eyJvZmZzZXQiOjIwfQ==" } Vòng lặp phân trang: trang đầu KHÔNG truyền `page_token`; trang sau truyền nguyên `next_page_token`; `next_page_token` rỗng = hết trang. 3 GIỚI HẠN (phía TikTok, không phải phía RioHub) — code phải tính tới: 1. Kết quả THEO PHẠM VI TỪNG CREATOR: không có tìm kiếm toàn sàn ẩn danh, kết quả là tập sản phẩm creator đó được phép quảng bá nên 2 creator có thể ra khác nhau. Vì vậy `creator_username` bắt buộc. 2. KHÔNG lọc được theo giá / % hoa hồng / danh mục / shop — TikTok chỉ nhận `keyword` ở tầng filter, chỉ SẮP XẾP theo 2 field trên. Muốn lọc giá/hoa hồng thì lấy về rồi lọc phía client. 3. KHÔNG có tìm theo ảnh (image search): không endpoint nào nhận ảnh đầu vào. Cách khớp chéo sàn thực dụng: chuẩn hoá tên (bỏ tên shop/emoji/mã SKU, giữ brand + dòng sản phẩm), search, lọc band giá +/-20%, rồi so ảnh phía client bằng `main_image_url`. Mã lỗi riêng: 422 `validation_error` (tham số sai — khoảng lọc âm, `min > max`, `keyword` quá 200 ký tự, `page_token` quá dài); 424 `creator_token_invalid` (token TikTok của CREATOR hỏng — KHÔNG phải key sai, đừng xoay key); 429 `tiktok_rate_limited` (chờ theo `Retry-After`); 503 `tiktok_scope_unavailable` (retry không giúp); 502 `tiktok_shop_error` (TikTok lỗi, retry có giãn cách). Object sản phẩm passthrough từ TikTok nên đọc PHÒNG THỦ: `id` hoặc `product_id`; `title` hoặc `product_name`; `main_image_url` hoặc `cover_url` hoặc `image_url`; `commission.rate` hoặc `commission_rate`. So hoa hồng thì dùng `observed_commission.commission_rate` (rate tổng thực tế), KHÔNG dùng `commission.rate` (chỉ rate chuẩn) — xem mục 4. ## 5) Postback (webhook) — RioHub POST về URL của bạn Cấu hình URL + sự kiện ở trang Developer (mục Postback URL); URL phải HTTPS. Event JSON: `event` ∈ { order.created, order.updated, order.refunded }; `event_id` (UUID) idempotency; `occurred_at` ISO-8601. `data` chứa các field như orders[] (status 1/2/3, order_status_raw = settlement_status gốc). `data.trace_id` / `data.trace_type`: mã click TikTok gắn vào đơn. `trace_type` = `SPECIFIC` ⇒ link `/links`, `trace_id` = sub_id (trùng `subid`); `GENERAL` ⇒ link `/general-links`, `trace_id` = `event_id` bạn gắn vào link. Link không mang mã nào ⇒ cả hai null. Đơn từ link `/general-links` có `subid` cố định = `open_api_san` (TikTok gán) — phân biệt bằng `trace_id`, không dùng subid. KHÔNG nhầm với `event_id` ở cấp ngoài (UUID của sự kiện webhook). Xác minh chữ ký — header `X-Riohub-Signature: t=<unix_ts>,v1=<hex>`, trong đó `v1 = HMAC-SHA256(key = signing secret, message = t + "." + raw_body)` (raw body nguyên văn, CHƯA parse JSON). So sánh bằng hàm constant-time. Signing secret lấy ở mục Postback URL — đọc từ env như API key. Yêu cầu bên nhận: - Trả HTTP `2xx` trong vòng 5 giây → xử lý nặng thì đẩy vào queue, trả 200 trước. - Không nhận `2xx` → RioHub thử lại theo backoff 1m → 5m → 30m → 2h → 6h (tối đa 6 lần) rồi đánh dấu `dead` ⇒ bên nhận PHẢI idempotent theo `event_id`. - Có thể đến lệch thứ tự: đừng ghi đè đơn bằng event cũ hơn; khi nghi ngờ thì gọi lại `GET /orders?order_id=...` để lấy trạng thái mới nhất. ## Yêu cầu code - Đọc key từ env; có cơ chế retry khi gặp 429 (tôn trọng `Retry-After`). - Bắt và log rõ các lỗi 401/403/404/422. - Hàm tạo link nhận (product_url, sub_id) và trả về `affiliate_link`.
Thử nghiệm trực tiếp

Nhập dữ liệu và gọi API thật bằng key của bạn — phản hồi hiển thị bên dưới.

Tự dùng key của tài khoản này (tạo sẵn 1 key "Playground" khi bạn bấm Gửi). Hoặc dán key khác để thử.

Gửi xong sẽ hiện danh sách ca bên dưới — bấm Xem đơn ở 1 ca để sang tab Đơn hàng với live_session_id điền sẵn.

Phản hồi
Postback qua Callback URL

RioHub POST JSON có chữ ký HMAC về URL của bạn mỗi khi đơn affiliate phát sinh / cập nhật / hoàn. Theo tài khoản (1 endpoint), áp dụng mọi creator đã kết nối. Bỏ trống URL nếu chỉ dùng Telegram bên dưới.

Đăng nhập để cấu hình Callback URL, lấy signing secret và xem lịch sử gửi.
Nếu dùng: phải HTTPS, trả về HTTP 2xx trong vòng 5s để xác nhận đã nhận. Để trống nếu chỉ dùng Telegram.
Giữ bí mật. Mỗi request kèm header X-Riohub-Signature: t=<ts>,v1=<hmac>.
Mẫu payload (body POST)
{ "event_id": "f1e2...-uuid", "event": "order.created", "occurred_at": "2026-06-10T04:18:00+00:00", "data": { "order_id": "5790...", "sku_id": "1730...", "product_id": "1729...", "product_name": "...", "creator_username": "your_creator", "currency": "VND", "quantity": 1, "refunded_quantity": 0, "fully_refunded": 0, "status": 1, "order_status_raw": "AWAITING PAYMENT", "prev_order_status_raw": null, "settled_at": null, "settled_at_iso": null, "price": "199000.00", "gmv": "199000.00", "actual_commission": null, "est_commission": "12.50", "subid": "abc-def-ghi-jkl", "sub1": "abc", "sub2": "def", "sub3": "ghi", "sub4": "jkl", "trace_id": "abc-def-ghi-jkl", "trace_type": "SPECIFIC" } }

Chi tiết field:

FieldTypeGiá trị / EnumMô tả
event_idstringUUIDID sự kiện, ổn định để chống xử lý trùng (idempotency).
eventstringorder.created · order.updated · order.refundedLoại sự kiện (trùng danh sách "Sự kiện đăng ký" ở trên).
occurred_atstringISO-8601Thời điểm phát sinh sự kiện.
data.order_idstring—Mã đơn TikTok.
data.sku_idstring—Mã SKU.
data.quantitynumber—Số lượng đặt.
data.refunded_quantitynumber—SL đã hoàn.
data.fully_refundednumber0 · 11 = đã hoàn toàn bộ.
data.statusnumber1 · 2 · 31=pending · 2=settled · 3=refunded/cancelled.
data.order_status_rawstringAWAITING PAYMENT · To-SETTLE · SETTLED · REFUNDEDTrạng thái gốc TikTok (passthrough).
data.prev_order_status_rawstring · nullnull với order.createdTrạng thái gốc trước khi cập nhật (để hiển thị cũ → mới); chỉ có ở order.updated.
data.settled_at_isostring · nullISO 8601, vd 2026-08-29T02:50:22ZNên dùng field này. Cùng giá trị với data.settled_at nhưng có hậu tố Z nên không thể đọc nhầm múi giờ.
data.settled_atstring · nullY-m-d H:i:s (UTC)Ngày quyết toán — gửi kèm ngay trong postback đơn chuyển sang đã chốt (status = 2), khỏi phải gọi lại REST. Ý nghĩa null: xem field cùng tên ở orders[].
data.pricestring · null—Đơn giá 1 sản phẩm.
data.gmvstring · null—GMV = đơn giá × số lượng.
data.actual_commissionstring · nullnull khi chưa đối soátHoa hồng thực nhận.
data.est_commissionstring · null—Hoa hồng ước tính (dùng khi chưa đối soát).
data.subidstringvd abc-def-ghi-jklSub_id đầy đủ (tag gốc) gắn vào link affiliate.
data.sub1 … data.sub4string—subid tách theo dấu - (vị trí 1–4); rỗng nếu không có. Đơn từ link /general-links luôn có subid = open_api_san (TikTok gán) — phân biệt bằng trace_id.
data.trace_idstring · nullvd abc-def-ghi-jkl · dl.5077.820601.abcMã theo dõi TikTok gắn vào đơn. Link tạo qua POST /links = đúng subid; link tạo qua /general-links = event_id bạn gắn vào link. null nếu link không mang mã nào. Khác event_id ở cấp ngoài (UUID của sự kiện webhook).
data.trace_typestring · nullGENERAL · SPECIFICSPECIFIC = link /links, trace_id là sub_id; GENERAL = link /general-links, trace_id là event_id bạn gắn; null khi không có trace.

Các field còn lại trong data (product_id, product_name, creator_username, currency, returned_quantity) cùng kiểu & ý nghĩa như ở API Lấy đơn hàng.

Xác minh chữ ký

v1 = HMAC-SHA256(secret, t + "." + raw_body) — ký trên body thô (chưa parse JSON), so sánh constant-time.

<?php $secret = 'YOUR_SIGNING_SECRET'; $raw = file_get_contents('php://input'); // body thô, KHÔNG decode $sig = $_SERVER['HTTP_X_RIOHUB_SIGNATURE'] ?? ''; // "t=...,v1=..." parse_str(str_replace(',', '&', $sig), $p); $expected = hash_hmac('sha256', ($p['t'] ?? '') . '.' . $raw, $secret); if (!hash_equals($expected, $p['v1'] ?? '')) { http_response_code(403); exit('bad signature'); } // (tuỳ chọn) chống replay: từ chối nếu abs(time() - $p['t']) > 300 $event = json_decode($raw, true); // ... xử lý $event['event'] / $event['data'] ... // $event['data']['trace_id']: mã click (event_id) khi trace_type = 'GENERAL'; = sub_id khi 'SPECIFIC' http_response_code(200); echo 'ok';
// Express — PHẢI lấy body thô: express.raw() cho route này, KHÔNG dùng express.json() const crypto = require('crypto'); const express = require('express'); const app = express(); const SECRET = process.env.RIOHUB_SIGNING_SECRET; app.post('/riohub/webhook', express.raw({ type: 'application/json' }), (req, res) => { const raw = req.body; // Buffer const parts = Object.fromEntries(String(req.get('X-Riohub-Signature') || '') .split(',').map(kv => kv.trim().split('='))); // { t, v1 } const expected = crypto.createHmac('sha256', SECRET) .update(parts.t + '.' + raw.toString('utf8')).digest('hex'); const ok = parts.v1 && parts.v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected)); if (!ok) return res.status(403).send('bad signature'); // (tuỳ chọn) chống replay: từ chối nếu |now - t| > 300 giây const event = JSON.parse(raw.toString('utf8')); res.status(200).send('ok'); // trả 2xx trong 5s, xử lý nặng đẩy queue // ... xử lý event.event / event.data (idempotent theo event.event_id) ... // event.data.trace_id: mã click (event_id) khi trace_type = 'GENERAL'; = sub_id khi 'SPECIFIC' }); app.listen(3000);
# Flask — request.get_data() là body thô import hmac, hashlib, json, os from flask import Flask, request app = Flask(__name__) SECRET = os.environ["RIOHUB_SIGNING_SECRET"].encode() @app.post("/riohub/webhook") def riohub_webhook(): raw = request.get_data() # bytes, KHÔNG dùng request.json để ký parts = dict(kv.strip().split("=", 1) for kv in request.headers.get("X-Riohub-Signature", "").split(",") if "=" in kv) expected = hmac.new(SECRET, parts.get("t", "").encode() + b"." + raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, parts.get("v1", "")): return "bad signature", 403 # (tuỳ chọn) chống replay: từ chối nếu abs(time.time() - int(parts["t"])) > 300 event = json.loads(raw) # ... xử lý event["event"] / event["data"] (idempotent theo event["event_id"]) ... # event["data"]["trace_id"]: mã click (event_id) khi trace_type = "GENERAL"; = sub_id khi "SPECIFIC" return "ok", 200

Retry: nếu không nhận 2xx, RioHub thử lại theo backoff 1m → 5m → 30m → 2h → 6h (tối đa 6 lần) rồi đánh dấu dead.

Lịch sử gửi gần đây
Thời điểmSự kiệnOrderPostback (HTTP)TelegramLần thửLỗi
—
Thông báo qua Telegram

Gửi 1 tin nhắn tóm tắt mỗi đơn về Telegram của bạn — kênh độc lập, dùng bot riêng. Lưu riêng, không ảnh hưởng cấu hình URL ở trên. Cách lấy token & chat ID ▾

Chưa bật
  1. Mở @BotFather trên Telegram → gửi /newbot → nhận bot token dạng 123456789:ABC-def....
  2. Nhắn cho bot (chat riêng), hoặc thêm bot vào group/channel.
  3. Lấy chat ID: mở https://api.telegram.org/bot<token>/getUpdates sau khi nhắn bot → copy chat.id. Channel công khai dùng @tenkenh.
Token được lưu phía máy chủ, không hiển thị lại.
Để trống & lưu = tắt Telegram.

Nút Gửi test bắn 1 tin ping tới mọi kênh đang bật (URL & Telegram).

Về TikTok Shop