Hướng dẫn tích hợp KiotViet API từ cách lấy Client ID, Client Secret, access token đến đăng ký webhook product.update và order.update.
Bạn muốn tích hợp KiotViet API với website nhưng chưa biết lấy Client ID ở đâu, xin access token như thế nào và đăng ký webhook ra sao? Bài viết này hướng dẫn toàn bộ quy trình kỹ thuật cần thiết để kết nối backend với KiotViet một cách an toàn.
Client ID và Client Secret cho phép backend xác thực với KiotViet Public API. Sau đó, webhook giúp KiotViet chủ động thông báo cho website khi sản phẩm hoặc đơn hàng thay đổi, thay vì hệ thống phải liên tục gọi API để kiểm tra.
Bài viết này chỉ tập trung vào hai nội dung:
Đăng ký kết nối và nhận Client ID/Client Secret.
Đăng ký webhook product.update và order.update.
Toàn bộ credential, access token, retailer và tên miền trong ví dụ đều là dữ liệu minh họa. Không đưa thông tin thật vào source code, tài liệu công khai hoặc Git.
Trước khi bắt đầu, bạn cần có:
Tài khoản quản trị gian hàng KiotViet.
Quyền kích hoạt kết nối API và cấp quyền dữ liệu.
Một backend có thể lưu credential an toàn.
Tên miền HTTPS công khai để nhận webhook.
Endpoint xử lý cập nhật sản phẩm và đơn hàng.
Không nên thực hiện luồng xác thực KiotViet trực tiếp trên frontend vì Client Secret sẽ bị lộ cho người dùng trình duyệt.
Trước khi gọi Public API, chủ gian hàng cần kích hoạt kết nối API trên KiotViet và cấp quyền cho ứng dụng bên thứ ba.
Tùy giao diện và gói dịch vụ đang sử dụng, tên hoặc vị trí menu có thể khác nhau. Về nguyên tắc, người quản trị gian hàng cần:
Đăng nhập KiotViet bằng tài khoản có quyền quản trị.
Mở khu vực thiết lập kết nối API hoặc kết nối ứng dụng.
Tạo một kết nối mới cho website/backend.
Bật các quyền dữ liệu cần thiết, đặc biệt là sản phẩm và đơn đặt hàng.
Kích hoạt kết nối.
Nhận Client ID và Client Secret do KiotViet cấp.
Ngoài hai credential trên, đội phát triển cần biết retailer code. Đây thường là phần đứng trước .kiotviet.vn trong địa chỉ gian hàng:
https://myshop.kiotviet.vn
^^^^^^
retailer code = myshop
Bộ thông tin tối thiểu sau khi đăng ký gồm:
Client ID: định danh của kết nối
Client Secret: khóa bí mật dùng để xin access token
Retailer: mã gian hàng KiotViet
Client Secret chỉ được lưu ở backend hoặc secret manager. Không đặt nó trong frontend, mobile app hay biến môi trường được build vào JavaScript phía trình duyệt.
Nên lưu thông tin kết nối dưới dạng environment variables:
KIOTVIET_API_URL=https://public.kiotapi.com
KIOTVIET_API_TOKEN_URL=https://id.kiotviet.vn/connect/token
KIOTVIET_GRANT_TYPE=client_credentials
KIOTVIET_CLIENT_ID=<your-client-id>
KIOTVIET_CLIENT_SECRET=<your-client-secret>
KIOTVIET_SCOPE=PublicApi.Access
KIOTVIET_RETAILER=<your-retailer-code>
File .env thật không được commit. Trên staging và production, credential nên được lưu trong secret manager của nền tảng triển khai.
KiotViet sử dụng OAuth 2.0 với grant type client_credentials. Backend gửi Client ID và Client Secret đến token endpoint dưới định dạng application/x-www-form-urlencoded:
curl --request POST 'https://id.kiotviet.vn/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=<your-client-id>' \
--data-urlencode 'client_secret=<your-client-secret>' \
--data-urlencode 'scope=PublicApi.Access'
Kết quả trả về có dạng tương tự:
{
"access_token": "<access-token>",
"expires_in": 86400,
"token_type": "Bearer"
}
Giá trị thực tế của expires_in cần được lấy từ response, không nên hard-code. Backend có thể cache access token trong RAM hoặc Redis và xin token mới trước khi token cũ hết hạn khoảng 60 giây.
Khi gọi KiotViet Public API, request cần hai header quan trọng:
Authorization: Bearer <access-token>
Retailer: <your-retailer-code>
Có thể kiểm tra kết nối bằng cách lấy danh sách webhook hiện có:
curl 'https://public.kiotapi.com/webhooks' \
--header 'Authorization: Bearer <access-token>' \
--header 'Retailer: <your-retailer-code>'
Nếu request trả lỗi xác thực, hãy kiểm tra Client ID, Client Secret, scope và retailer. Retailer phải thuộc đúng gian hàng đã cấp credential.
Webhook KiotViet là cơ chế gửi thông báo tự động từ KiotViet đến một URL do hệ thống của bạn cung cấp. Khi có dữ liệu thay đổi, KiotViet gọi endpoint tương ứng và gửi payload chứa thông tin sự kiện.
So với việc polling API theo lịch, webhook mang lại ba lợi ích chính:
Website nhận thay đổi gần thời gian thực.
Giảm số request không cần thiết đến Public API.
Dễ tách riêng luồng cập nhật sản phẩm và đơn hàng.
Trong phạm vi bài viết này, chúng ta sử dụng hai loại webhook:
product.update: thông báo sản phẩm hoặc tồn kho thay đổi.
order.update: thông báo đơn hàng thay đổi.
KiotViet cần gọi được endpoint của hệ thống từ Internet. Trong ví dụ này, backend cung cấp hai URL:
POST https://example.com/api/kiotviet/webhook/update-product
POST https://example.com/api/kiotviet/webhook/update-orderHai endpoint nên:
Sử dụng HTTPS với chứng chỉ hợp lệ.
Chấp nhận request POST chứa JSON.
Trả HTTP 200 nhanh khi đã tiếp nhận dữ liệu.
Kiểm tra payload trước khi xử lý.
Xử lý lặp an toàn vì một notification có thể được gửi lại.
Không ghi credential hoặc access token vào log.
Payload webhook chứa mảng Notifications; mỗi notification có Action và Data. Backend cần kiểm tra các trường này thay vì giả định payload luôn hợp lệ.
Ví dụ controller NestJS tối giản:
@Controller('kiotviet/webhook')
export class KiotVietWebhookController {
@Post('update-product')
@HttpCode(200)
async updateProduct(@Body() payload: any) {
if (!payload?.Notifications?.length) {
throw new BadRequestException('Invalid webhook payload');
}
// Đưa notification vào queue hoặc xử lý đồng bộ.
return { status: 'ok' };
}
@Post('update-order')
@HttpCode(200)
async updateOrder(@Body() payload: any) {
if (!payload?.Notifications?.length) {
return { status: 'ignored' };
}
// Đưa notification vào queue hoặc xử lý đồng bộ.
return { status: 'ok' };
}
}Trên production nên xác thực nguồn webhook nếu KiotViet cung cấp cơ chế phù hợp. Có thể bổ sung giới hạn kích thước request, rate limiting, idempotency và queue để endpoint không timeout khi nhận payload lớn.
product.updateWebhook product.update thông báo khi dữ liệu sản phẩm thay đổi trên KiotViet. Tùy payload và nghiệp vụ, backend có thể cập nhật lại giá, trạng thái bán hoặc tồn kho local.
curl --request POST 'https://public.kiotapi.com/webhooks' \
--header 'Authorization: Bearer <access-token>' \
--header 'Retailer: <your-retailer-code>' \
--header 'Content-Type: application/json' \
--data '{
"Webhook": {
"Type": "product.update",
"Url": "https://example.com/api/kiotviet/webhook/update-product",
"IsActive": true,
"Description": "Webhook update product"
}
}'Trong cách áp dụng hiện tại, backend xử lý theo trình tự:
Duyệt từng phần tử trong Notifications.
Lấy SKU từ trường Code.
Tìm sản phẩm hoặc biến thể local theo SKU.
Đọc Inventories trong notification.
Chọn tồn kho thuộc các branch đã cấu hình.
Upsert tồn kho và cập nhật sản phẩm local.
Không nên đồng bộ bằng tên sản phẩm vì tên có thể trùng hoặc thay đổi. SKU là khóa đối chiếu ổn định hơn. Nếu không tìm thấy SKU hoặc thiếu tồn kho cần thiết, hệ thống nên cảnh báo và bỏ qua thay vì cập nhật sai dữ liệu.
order.updateWebhook order.update thông báo thay đổi trạng thái hoặc thông tin đơn hàng từ KiotViet:
curl --request POST 'https://public.kiotapi.com/webhooks' \
--header 'Authorization: Bearer <access-token>' \
--header 'Retailer: <your-retailer-code>' \
--header 'Content-Type: application/json' \
--data '{
"Webhook": {
"Type": "order.update",
"Url": "https://example.com/api/kiotviet/webhook/update-order",
"IsActive": true,
"Description": "Webhook update order"
}
}'Backend nên kiểm tra Action trước khi xử lý:
for (const notification of payload.Notifications) {
if (!notification.Action?.startsWith('order.update')) continue;
for (const order of notification.Data ?? []) {
await updateLocalOrder(order);
}
}Trong cách triển khai hiện tại, hệ thống tìm đơn local theo Code, sau đó cập nhật trạng thái, ghi chú và thời điểm chỉnh sửa. Trạng thái KiotViet được ánh xạ sang trạng thái nội bộ:
pending -> pending
confirmed -> confirmed
delivered -> delivered
cancelled -> cancelledCần xác nhận giá trị trạng thái thực tế trong payload trước khi chốt bảng mapping. Không nên mặc định mọi trạng thái lạ thành pending, vì có thể làm trạng thái đơn bị lùi. An toàn hơn là log trạng thái chưa hỗ trợ và không cập nhật cho đến khi có mapping rõ ràng.
Gọi lại danh sách webhook:
curl 'https://public.kiotapi.com/webhooks' \
--header 'Authorization: Bearer <access-token>' \
--header 'Retailer: <your-retailer-code>'Kiểm tra:
Có đủ product.update và order.update.
IsActive đang là true.
URL đúng môi trường staging hoặc production.
Webhook thuộc đúng retailer.
ID của từng webhook đã được lưu để quản lý hoặc gỡ bỏ khi cần.
Sau đó thực hiện kiểm thử thực tế:
Sửa một sản phẩm test trên KiotViet và xác nhận backend nhận product.update.
Đối chiếu dữ liệu local sau khi xử lý.
Thay đổi trạng thái một đơn test trên KiotViet.
Xác nhận backend nhận order.update và cập nhật đúng đơn local.
Gửi lại cùng payload để kiểm tra khả năng xử lý lặp.
Khi chuyển từ store cũ sang store mới, không nên xóa webhook cũ ngay. Hãy đăng ký và kiểm thử webhook mới trước, theo dõi ổn định rồi mới vô hiệu hóa kết nối cũ theo kế hoạch rollback.
Sử dụng ID nhận được khi tạo hoặc khi gọi danh sách webhook:
curl --request DELETE \
'https://public.kiotapi.com/webhooks/<webhook-id>' \
--header 'Authorization: Bearer <access-token>' \
--header 'Retailer: <your-retailer-code>'
Đây là thao tác làm ngừng luồng đồng bộ, vì vậy phải kiểm tra chính xác webhook ID và retailer trước khi thực hiện.
Kết nối API KiotViet đang hoạt động và có đúng quyền.
Client ID và Client Secret chỉ nằm trong secret manager.
Access token được cache và tự làm mới trước khi hết hạn.
Retailer khớp với credential.
Endpoint webhook dùng HTTPS và truy cập được từ Internet.
product.update và order.update đã active và đúng URL.
Payload không hợp lệ được xử lý an toàn.
Xử lý webhook có idempotency.
Có log, monitoring và cảnh báo khi webhook thất bại.
Không log Client Secret hoặc access token.
Có kế hoạch rollback khi đổi store hoặc credential.
Client ID định danh kết nối API, còn Client Secret là khóa bí mật để backend xin access token. Hai giá trị này không được gửi xuống frontend hoặc công khai trong repository.
Retailer là mã gian hàng, thường là phần đứng trước .kiotviet.vn trong URL quản trị. Header Retailer phải thuộc đúng gian hàng đã cấp Client ID và Client Secret.
Không. Backend nên cache token theo thời hạn expires_in và làm mới trước khi hết hạn. Điều này giảm độ trễ và tránh tạo request xác thực không cần thiết.
product.update dùng để làm gì?Webhook này thông báo khi dữ liệu sản phẩm thay đổi. Backend có thể dùng SKU trong payload để tìm sản phẩm local rồi cập nhật giá, trạng thái hoặc tồn kho theo nhu cầu nghiệp vụ.
order.update dùng để làm gì?Webhook này thông báo khi đơn hàng trên KiotViet thay đổi. Hệ thống thường dùng mã đơn để tìm bản ghi local và đồng bộ trạng thái, ghi chú hoặc thời gian cập nhật.
Hãy kiểm tra URL có truy cập công khai qua HTTPS không, webhook có đang active không, retailer có đúng không và endpoint có chấp nhận POST JSON hay không. Log tại reverse proxy và backend sẽ giúp xác định request có đến hệ thống hay bị chặn trước đó.
Client ID và Client Secret tạo ra kênh xác thực để backend làm việc với KiotViet Public API. Hai webhook product.update và order.update tạo ra chiều đồng bộ ngược, giúp website nhận thay đổi sản phẩm và đơn hàng gần thời gian thực.
Phần gọi API tương đối ngắn; chất lượng của integration nằm ở cách bảo vệ credential, xác minh retailer, kiểm tra payload, xử lý lặp và giám sát webhook. Hoàn thành tốt các điểm này sẽ tạo nền tảng an toàn cho những luồng đồng bộ KiotViet tiếp theo.
Nếu bạn đang triển khai thực tế, hãy thử toàn bộ quy trình trên staging trước: xin token, đăng ký hai webhook, thay đổi một sản phẩm và một đơn hàng test, sau đó mới áp dụng credential cùng endpoint production.