Chuyển tới nội dung chính

Cài đặt Webhook

Chức năng Webhook trong Mini Gift cho phép hệ thống của bạn nhận thông báo theo thời gian thực mỗi khi có sự kiện xảy ra — ví dụ: người dùng nhận quà, hoàn thành khảo sát, tích điểm... Thay vì phải liên tục gọi API để kiểm tra, webhook sẽ tự động gửi dữ liệu đến URL mà bạn cấu hình.

Tại đây, bạn có thể:

  • Tạo và cấu hình webhook với URL nhận dữ liệu
  • Chọn phương thức bảo mật: HMAC-SHA256, API Key hoặc không xác thực
  • Bật / tắt webhook và tự động thử lại khi gửi thất bại
  • Quản lý danh sách event — bật/tắt và gửi test từng event
  • Xem nhật ký các request đã gửi
Webhook là gì?

Webhook là cơ chế gửi dữ liệu tự động từ Mini Gift đến hệ thống bên ngoài (server của bạn) thông qua HTTP POST request mỗi khi có sự kiện xảy ra. Bạn chỉ cần cung cấp một URL endpoint để nhận dữ liệu.


1. Truy cập trang Webhook

Từ thanh menu bên trái:

  1. Chọn Quản lý cửa hàng
  2. Nhấn vào Quản lý liên kết
  3. Chuyển sang tab Webhook
Truy cập trang Webhook từ menu Quản lý cửa hàng → Quản lý liên kết → Tab Webhook

2. Tạo Webhook mới

Điền các thông tin sau để tạo webhook:

TrườngMô tảBắt buộc
URL WebhookĐịa chỉ endpoint nhận dữ liệu (HTTP POST)
Phương thức bảo mậtChọn cách xác thực request: HMAC-SHA256, API Key hoặc Không xác thực
Mô tảGhi chú mục đích sử dụng webhookKhông

Sau khi điền đầy đủ, nhấn Tạo webhook.

Form điền thông tin tạo webhook mới — URL, phương thức bảo mật và mô tả

Webhook Secret

Ngay sau khi tạo thành công, hệ thống sẽ hiển thị Webhook Secret (chữ ký số). Đây là lần duy nhất bạn có thể xem giá trị đầy đủ — hãy sao chép và lưu lại ngay.

Webhook secret hiển thị lần đầu sau khi tạo thành công
Lưu ý quan trọng

Webhook Secret chỉ hiển thị đầy đủ một lần duy nhất sau khi tạo. Nếu bạn quên sao chép, bạn sẽ cần tạo lại chữ ký số mới (xem mục 5).


3. Cài đặt Webhook

Sau khi tạo webhook thành công, hệ thống hiển thị thêm các tuỳ chọn cài đặt.

3.1. Trạng thái & Tự động thử lại

Cài đặtMô tả
Trạng tháiBật / tắt webhook — khi tắt, toàn bộ event sẽ ngừng gửi
Tự động thử lạiKhi bật, hệ thống sẽ tự động gửi lại request nếu lần gửi trước thất bại
Cài đặt bật/tắt trạng thái webhook và tự động thử lại khi failed
Khuyến nghị

Nên bật Tự động thử lại để đảm bảo không bỏ sót event quan trọng khi server của bạn tạm thời gặp sự cố.

3.2. Phương thức bảo mật

Mini Gift hỗ trợ 3 phương thức để bảo vệ webhook của bạn:

HMAC-SHA256

Hệ thống sử dụng Webhook Secret để tạo chữ ký số (HMAC-SHA256) cho mỗi request. Chữ ký được ký vào body của request — server của bạn dùng secret để xác minh rằng request thực sự đến từ Mini Gift.

Phương thức bảo mật HMAC-SHA256 — ký chữ ký số vào body request
Được khuyến nghị

HMAC-SHA256 là phương thức bảo mật an toàn nhất — đảm bảo cả tính xác thực và toàn vẹn dữ liệu của mỗi request.

Cách xác minh chữ ký trên server của bạn:

Khi Mini Gift gửi webhook, request sẽ kèm theo header chứa chữ ký (signature). Phía server của bạn cần thực hiện các bước sau để xác minh request là hợp lệ:

  1. Lấy chữ ký từ header của request nhận được
  2. Tính lại chữ ký bằng cách dùng HMAC-SHA256 với Webhook Secret (đã lưu ở bước tạo webhook) để ký vào raw body của request
  3. So sánh chữ ký vừa tính với chữ ký nhận được từ header — nếu trùng khớp, request là hợp lệ và đến từ Mini Gift

Ví dụ xác minh bằng Node.js:

const crypto = require("crypto");

function verifyWebhookSignature(secret, signature, rawBody) {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(rawBody, "utf8")
.digest("hex");

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature),
);
}

// Sử dụng trong handler
app.post("/webhook", (req, res) => {
const signature = req.headers["x-webhook-signature"];
const isValid = verifyWebhookSignature(
"YOUR_WEBHOOK_SECRET",
signature,
req.rawBody,
);

if (!isValid) {
return res.status(401).send("Invalid signature");
}

// Xử lý event...
res.status(200).send("OK");
});
Lưu ý bảo mật
  • Luôn dùng timingSafeEqual (hoặc hàm so sánh constant-time tương đương) thay vì === để so sánh chữ ký — tránh tấn công timing attack. - Dùng raw body (chuỗi gốc) chứ không phải parsed JSON, vì bất kỳ thay đổi nhỏ nào trong body sẽ tạo ra chữ ký khác. - Không hardcode secret trong code — hãy lưu trong biến môi trường (environment variable).

API Key (Header tuỳ chỉnh)

Bạn cấu hình một header tuỳ chỉnh chứa API Key. Mỗi request webhook sẽ gửi kèm header này — server của bạn kiểm tra giá trị header để xác thực.

Phương thức bảo mật API Key — cấu hình header tuỳ chỉnh

Không xác thực

Không sử dụng bất kỳ phương thức bảo mật nào. Request webhook được gửi trực tiếp mà không kèm chữ ký hay API Key.

Phương thức không xác thực — webhook gửi trực tiếp không kèm bảo mật
Cảnh báo

Chỉ nên chọn Không xác thực khi đang test hoặc trong môi trường nội bộ. Trong production, hãy sử dụng HMAC-SHA256 hoặc API Key để bảo vệ endpoint.


4. Danh sách Event

Webhook hỗ trợ 15 event thuộc 4 nhóm chính. Mỗi event có nút Bật / Tắt riêng và nút Gửi test ngay bên cạnh để kiểm tra kết nối.

Danh sách event webhook — phần 1: Quà tặng và Khảo sát
Danh sách event webhook — phần 2: Điểm thưởng và Người dùng
Gửi test

Nhấn nút Gửi test bên cạnh mỗi event để gửi một request mẫu đến URL webhook. Hãy kiểm tra server của bạn nhận được dữ liệu đúng định dạng trước khi bật event chính thức.

Format chung

Tất cả webhook đều gửi request với body JSON theo cấu trúc chung:

{
"event": "tên_event",
"data": { ... },
"sentAt": "2026-06-30T02:53:24.167Z"
}
TrườngKiểuMô tả
eventstringTên event đã xảy ra (ví dụ: gift.claimed)
dataobjectDữ liệu chi tiết — khác nhau tuỳ từng event (xem bên dưới)
sentAtstringThời điểm gửi webhook (ISO 8601)

4.1. Quà tặng

gift.created · gift.updated.info · gift.deleted

Khi quà tặng được tạo mới, cập nhật hoặc xóa, data chứa toàn bộ thông tin quà tặng:

{
"event": "gift.created",
"data": {
"gift": {
"_id": "6a326d720806a3b1217f413c",
"name": "Voucher giảm 50K",
"giftCode": "2358360026902",
"status": "active",
"deliveryMethod": "in_store",
"integrationType": "direct_claim",
"redeemMethod": "self_confirm",
"image": "",
"winDisplayImage": "",
"shopId": "68f0b11278b98d763e096fe2",
"createdAt": "2026-06-17T09:48:34.538Z",
"updatedAt": "2026-06-29T09:10:36.182Z"
}
},
"sentAt": "2026-06-29T09:10:36.204Z"
}
TrườngKiểuMô tả
gift._idstringID quà tặng
gift.namestringTên quà tặng
gift.giftCodestringMã quà tặng
gift.statusstringTrạng thái (active, ...)
gift.deliveryMethodstringPhương thức giao quà (in_store, ...)
gift.integrationTypestringLoại tích hợp (direct_claim, ...)
gift.redeemMethodstringPhương thức đổi quà (self_confirm, ...)
gift.imagestringURL hình ảnh quà tặng
gift.shopIdstringID cửa hàng
gift.createdAtstringThời điểm tạo (ISO 8601)
gift.updatedAtstringThời điểm cập nhật gần nhất (ISO 8601)

gift.claimed

Khi người dùng nhận quà từ chiến dịch:

{
"event": "gift.claimed",
"data": {
"campaignId": "6a3b3dc828ddc2af709ea379",
"userGiftId": "6a432fa44c50719e9de15b2d",
"userId": "6a1429a02779e58b50191906"
},
"sentAt": "2026-06-30T03:00:00.000Z"
}
TrườngKiểuMô tả
campaignIdstringID chiến dịch
userGiftIdstringID bản ghi nhận quà
userIdstringID người dùng nhận quà

gift.redeemed

Khi người dùng đổi quà:

{
"event": "gift.redeemed",
"data": {
"campaignId": "6a3b3dc828ddc2af709ea379",
"userGiftId": "6a432fa44c50719e9de15b2d",
"userId": "6a1429a02779e58b50191906"
},
"sentAt": "2026-06-30T03:00:00.000Z"
}
TrườngKiểuMô tả
campaignIdstring | undefinedID chiến dịch (có thể không có nếu đổi quà ngoài chiến dịch)
userGiftIdstringID bản ghi nhận quà
userIdstringID người dùng đổi quà

gift.claimGiftByExchangePoints

Khi người dùng đổi điểm lấy quà:

{
"event": "gift.claimGiftByExchangePoints",
"data": {
"userGiftId": "6a432fa44c50719e9de15b2d",
"userId": "6a1429a02779e58b50191906"
},
"sentAt": "2026-06-30T03:00:00.000Z"
}
TrườngKiểuMô tả
userGiftIdstringID bản ghi nhận quà
userIdstringID người dùng đổi điểm

4.2. Khảo sát

survey.created · survey.updated.info · survey.deleted

Khi khảo sát được tạo mới, cập nhật hoặc xóa, data chứa toàn bộ thông tin khảo sát bao gồm danh sách câu hỏi:

{
"event": "survey.created",
"data": {
"survey": {
"_id": "6a3e4b66e66444b49090df99",
"name": "Khảo sát trải nghiệm khách hàng",
"status": "active",
"coverImage": "",
"questions": [
{
"id": "c3d508ab-4b80-4f4e-814f-8c34db60a5bb",
"question": "Bạn đánh giá dịch vụ như thế nào?",
"type": "single-select",
"options": ["Tốt", "Trung bình", "Kém"],
"required": true,
"allowOther": false
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"question": "Góp ý thêm",
"type": "text",
"options": [],
"required": false,
"allowOther": false
}
],
"shopId": "68f0b11278b98d763e096fe2",
"createdAt": "2026-06-26T09:50:30.487Z",
"updatedAt": "2026-06-26T10:14:39.413Z"
}
},
"sentAt": "2026-06-30T02:58:28.828Z"
}
TrườngKiểuMô tả
survey._idstringID khảo sát
survey.namestringTên khảo sát
survey.statusstringTrạng thái (active, ...)
survey.coverImagestringURL ảnh bìa
survey.questionsarrayDanh sách câu hỏi (xem bảng bên dưới)
survey.shopIdstringID cửa hàng
survey.createdAtstringThời điểm tạo (ISO 8601)
survey.updatedAtstringThời điểm cập nhật gần nhất (ISO 8601)

Cấu trúc mỗi câu hỏi trong questions:

TrườngKiểuMô tả
idstringID câu hỏi
questionstringNội dung câu hỏi
typestringLoại câu hỏi: text, single-select, multi-select, ...
optionsarrayDanh sách lựa chọn (rỗng nếu typetext)
requiredbooleanCâu hỏi bắt buộc hay không
allowOtherbooleanCho phép nhập câu trả lời khác

survey.completed

Khi người dùng hoàn thành khảo sát, data chứa danh sách câu trả lời:

{
"event": "survey.completed",
"data": {
"campaignId": "6a3b3dc828ddc2af709ea379",
"surveyId": "6a3a3a496ef2413c751b9877",
"userId": "6a1429a02779e58b50191906",
"answers": [
{
"questionId": "8a04c4af-da98-4da8-92ed-17ff21dd2505",
"questionText": "Vị trí công tác của Anh/Chị?",
"questionType": "single-select",
"answer": ["Chủ doanh nghiệp"]
},
{
"questionId": "ad96995a-aab1-4fdd-9405-3bdd57b7dc58",
"questionText": "Anh/Chị quan tâm nhóm sản phẩm nào?",
"questionType": "multi-select",
"answer": ["Đồ gia dụng", "Nội thất"]
}
]
},
"sentAt": "2026-06-30T03:22:42.992Z"
}
TrườngKiểuMô tả
campaignIdstringID chiến dịch
surveyIdstringID khảo sát
userIdstringID người dùng
answersarrayDanh sách câu trả lời (xem bảng bên dưới)

Cấu trúc mỗi câu trả lời trong answers:

TrườngKiểuMô tả
questionIdstringID câu hỏi
questionTextstringNội dung câu hỏi
questionTypestringLoại câu hỏi (text, single-select, multi-select, ...)
answerstring[]Mảng câu trả lời
Ghi chú

Trường answer luôn là mảng — kể cả với câu hỏi single-select hay text (mảng 1 phần tử). Điều này giúp format thống nhất khi xử lý.


4.3. Điểm thưởng

points.claimed

Khi người dùng tích điểm từ chiến dịch:

{
"event": "points.claimed",
"data": {
"campaignId": "6a3b3dc828ddc2af709ea379",
"userId": "6a1429a02779e58b50191906"
},
"sentAt": "2026-06-30T03:00:00.000Z"
}
TrườngKiểuMô tả
campaignIdstringID chiến dịch
userIdstringID người dùng tích điểm

4.4. Người dùng

user.providedPhone

Khi người dùng xác thực danh tính lần đầu bằng số điện thoại (bắt buộc khi truy cập tab Cá nhân):

{
"event": "user.providedPhone",
"data": {
"userId": "6a1429a02779e58b50191906",
"phone": "0901234567"
},
"sentAt": "2026-06-30T03:00:00.000Z"
}
TrườngKiểuMô tả
userIdstringID người dùng
phonestringSố điện thoại đã xác thực

user.followedOA

Khi người dùng theo dõi OA của cửa hàng trên Zalo Mini App:

{
"event": "user.followedOA",
"data": {
"userId": "6a1429a02779e58b50191906",
"zaloIdByOA": "8734521906453"
},
"sentAt": "2026-06-30T03:00:00.000Z"
}
TrườngKiểuMô tả
userIdstringID người dùng
zaloIdByOAstringID Zalo của người dùng theo OA

5. Tạo lại chữ ký số

Nếu bạn nghi ngờ Webhook Secret bị lộ hoặc cần thay đổi, bạn có thể tạo lại chữ ký số mới.

  1. Nhấn nút Tạo chữ ký mới — bên cạnh có nút Sao chép để copy giá trị hiện tại
  2. Hệ thống hiển thị popup xác nhận — nhấn Xác nhận để thay đổi ngay lập tức
Nút tạo lại chữ ký số và nút sao chép bên cạnh
Popup xác nhận thay đổi chữ ký số — nhấn Xác nhận để thay đổi ngay
Lưu ý

Sau khi tạo lại chữ ký số, secret cũ sẽ ngừng hoạt động ngay lập tức. Hãy cập nhật secret mới trên server của bạn để tránh gián đoạn nhận webhook.


6. Nhật ký Webhook

Nhấn nút Xem nhật ký để theo dõi lịch sử các request đã gửi. Trang nhật ký hiển thị:

Thông tinMô tả
Thời gianThời điểm gửi request
Sự kiệnEvent nào đã trigger webhook
HTTP StatusMã trạng thái HTTP trả về (200, 404, 500...)
Kết quảThành công hoặc Thất bại
Lần thửSố lần đã gửi (bao gồm retry)
Thời gian phản hồiThời gian server phản hồi request
LỗiThông tin lỗi nếu có
Trang nhật ký webhook — danh sách các request đã gửi với bộ lọc theo kết quả và sự kiện

Bạn có thể sử dụng bộ lọc để tìm nhanh:

  • Lọc theo kết quả: Thành công / Thất bại
  • Lọc theo sự kiện: Chọn event cụ thể cần xem
Mẹo debug

Khi webhook gửi thất bại, hãy kiểm tra cột HTTP StatusLỗi trong nhật ký để xác định nguyên nhân. Các lỗi phổ biến: URL không tồn tại (404), server quá tải (503), hoặc timeout.