Giao thức CLV1 đã công khaiĐọc đặc tả
Mục lục thư viện

Thư viện tài liệuTích hợp

Webhook

Webhook đẩy sự kiện của một ứng dụng (license, thiết bị, phiên, người dùng, Protect 24/7, bản phát hành) về server của bạn bằng một HTTP `POST` có chữ ký HMAC, chỉ vài giây sau khi sự việc xảy ra. Bạn dùng nó để đồng bộ CRM, gửi email cho khách, mở ticket hay chạy tự động hoá mà không cần gọi API dò liên tục.

11 phút đọc

Cách webhook hoạt động

  • Mỗi webhook là một endpoint gắn với một ứng dụng và chỉ nhận những sự kiện bạn chọn. Một ứng dụng có thể có nhiều endpoint; sự kiện khớp nhiều endpoint thì mỗi endpoint nhận một bản riêng.
  • Mỗi lần gửi được ký bằng signing secret riêng của endpoint, tự thử lại khi lỗi trong khoảng 3 ngày và được lưu vào nhật ký để bạn xem lại hoặc gửi lại.
  • Mọi thành viên workspace đều xem được trang webhook. Chỉ Quản trị và Chủ sở hữu mới tạo, bật tắt, gửi thử, đổi bí mật, xoá và gửi lại.

Tạo endpoint

  1. Mở ứng dụng, vào Tích hợp › Webhook.
  2. Bấm Thêm webhook. Bảng Webhook mới mở ra.
  3. Điền Địa chỉ nhận: URL đầy đủ của endpoint, tối đa 500 ký tự, ví dụ https://example.com/clavis.
  4. Điền Ghi chú nếu muốn (tối đa 200 ký tự), ví dụ "Đồng bộ CRM". Ghi chú hiện dưới URL trong danh sách.
  5. Ở mục Sự kiện, tick những sự kiện cần nhận. Danh sách được nhóm theo tiền tố (license, device, session…), và license.created cùng security.incident được chọn sẵn. Phải chọn ít nhất một sự kiện.
  6. Bấm Tạo webhook. Hộp Signing secret hiện ra ngay (xem phần dưới).

Yêu cầu với địa chỉ nhận:

  • Dùng https://. Lúc gửi, tên miền phải phân giải ra địa chỉ IP công khai: localhost và các dải nội bộ như 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 bị từ chối.
  • Trả mã 2xx trong vòng 10 giây. Chuyển hướng 3xx không được theo và bị tính là lỗi.

Dashboard chưa có chỗ sửa URL, ghi chú hay danh sách sự kiện của endpoint đã tạo. Muốn thay đổi, bạn xoá endpoint (biểu tượng thùng rác Xoá, xác nhận "Xoá webhook này?") rồi tạo lại. Endpoint mới có signing secret mới, và nhật ký gửi của endpoint cũ bị xoá theo.

Danh mục sự kiện

Sự kiệnKhi nàoTrường chính trong data
license.createdTạo license từ dashboard, Seller API hoặc cổng đại lý; cả lô là một sự kiệntargetType, targetId, count
license.activatedLicense được kích hoạt lần đầu từ phần mềmlicenseId, deviceId
license.expiringLicense đi vào khoảng báo trước hết hạn của gói license, mỗi kỳ hạn một lầnlicenseId, expiresAt
license.expiredLicense qua ngày hết hạn cộng số ngày ân hạnlicenseId, expiresAt
license.suspendedTạm ngưng license, đơn lẻ hoặc hàng loạttargetType, targetId
license.revokedThu hồi license, đơn lẻ hoặc hàng loạttargetType, targetId
license.extendedGia hạn license, đơn lẻ hoặc hàng loạttargetType, targetId
validation.failedServer từ chối một yêu cầu của phần mềmreason (mã lỗi), event, licenseId, ip
device.addedMột máy mới gắn vào licenselicenseId, deviceId, platform
device.removedGỡ máy khỏi license từ dashboard hoặc Seller APItargetType, targetId
device.blockedMột máy bị chặntargetType, targetId
device.transferredKhách tự chuyển máy trong phần mềm hoặc ở cổng khách hànglicenseId, deviceId, by
session.startedPhần mềm đăng nhập thành công, phiên mới được mởlicenseId, deviceId, sessionId, endUserId
session.endedPhần mềm gọi logout()sessionId, licenseId, deviceId
session.revokedThu hồi phiên từ dashboard hoặc Seller APItargetType, targetId
user.createdTạo tài khoản người dùng cuối từ dashboard hoặc Seller APItargetType, targetId
user.bannedCấm một tài khoản người dùng cuốitargetType, targetId
security.incidentProtect 24/7 mở một sự cố mớiincidentId, signal, severity, subjectType, subjectId
security.actionProtect 24/7 tự xử lý theo Xử lý theo mức rủi roactionId, action, subjectType, subjectId, reason
release.publishedMột bản phát hành được công bốtargetType, targetId
reseller.credit_lowTín dụng của một đại lý tụt dưới mức đủ cho 5 key của gói đắt nhất họ bán, mỗi lần vượt ngưỡng một lầnresellerId, balance
webhook.testBạn bấm Gửi thửmessage

Vài điều cần biết:

  • Thao tác từ dashboard và Seller API luôn có targetType, targetId cùng chi tiết của thao tác đó; Seller API thêm apiKeyId và apiKeyName. Hãy đọc data một cách linh hoạt, chấp nhận trường thiếu hoặc trường mới.
  • Sự kiện xuất phát từ phần mềm (license.activated, validation.failed, device.added, device.transferred trong phần mềm, session.started, session.ended) chỉ được ghi lại khi ứng dụng có ít nhất một webhook đang bật đăng ký chúng. Server nhớ danh sách này khoảng 30 giây, nên sự kiện xảy ra ngay sau khi bạn tạo hoặc bật lại endpoint có thể bị bỏ qua.
  • Ở phiên bản hiện tại, tài khoản do phần mềm tự đăng ký bằng register() chưa phát user.created.
  • Thời hạn license được kiểm tra mỗi giờ, nên license.expiring và license.expired có thể đến trễ tối đa khoảng một giờ.

Signing secret

Ngay sau khi tạo, hộp Signing secret hiện secret dạng whsec_… kèm dòng "Chỉ hiện một lần, lưu ngay vào nơi an toàn" và sẵn một đoạn mã Node để kiểm tra chữ ký. Sao chép secret vào kho bí mật của server nhận, rồi bấm Đã lưu. ClavisAUH lưu secret ở dạng mã hoá và không có cách nào hiện lại.

Để đổi secret, bấm Đổi bí mật trên thẻ endpoint. Không có bước xác nhận: secret mới thay thế ngay và hiện trong cùng hộp Signing secret. Mọi lần gửi từ lúc đó, kể cả lần thử lại của sự kiện cũ, đều ký bằng secret mới.

Payload và header

Mỗi lần gửi là một POST với body JSON:

{
  "id": "evt_…",
  "type": "license.activated",
  "createdAt": "2026-11-02T08:15:30.000Z",
  "appId": "app_…",
  "data": { "licenseId": "lic_…", "deviceId": "dev_…" }
}
TrườngÝ nghĩa
idMã sự kiện. Giữ nguyên qua mọi lần thử lại và gửi lại, giống nhau giữa các endpoint. Dùng nó để chống xử lý trùng. Sự kiện thử có dạng evt_test_…
typeTên sự kiện trong danh mục
createdAtThời điểm sự kiện xảy ra, ISO 8601 theo UTC
appIdỨng dụng phát sinh sự kiện
dataChi tiết, khác nhau theo sự kiện và nguồn

Header đi kèm:

HeaderGiá trị
Content-Typeapplication/json
User-AgentClavisAUH-Webhooks/1
Clavis-Signaturet=<unix>,v1=<hex>
Clavis-EventTên sự kiện, giống type
Clavis-DeliveryMã lần gửi trong nhật ký. Giữ nguyên qua các lần thử lại tự động, đổi khi bạn bấm Gửi lại

Xác minh Clavis-Signature

v1 là HMAC-SHA256 dạng hex của chuỗi <t>.<body>, trong đó t là thời điểm ký tính bằng giây Unix, body là body thô đúng như nhận được, và khoá là toàn bộ chuỗi whsec_…. t được tính lại ở mỗi lần gửi, nên kiểm tra độ lệch 5 phút vẫn đúng với cả các lần thử lại.

import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";

// Clavis-Signature: t=<unix>,v1=<hex>
export function verifyClavis(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false; // lệch tối đa 5 phút
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest();
  const received = Buffer.from(parts.v1 ?? "", "hex");
  return received.length === expected.length && timingSafeEqual(received, expected);
}

const app = express();

// Giữ body thô để kiểm tra chữ ký trước khi parse JSON
app.post("/clavis", express.raw({ type: "application/json" }), async (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verifyClavis(raw, req.get("clavis-signature") ?? "", process.env.CLAVIS_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(raw);
  await saveOnce(event.id, event); // ghi theo event.id, bỏ qua nếu đã có; việc nặng làm ở job nền
  res.sendStatus(204);
});

Nguyên tắc cho server nhận:

  • Kiểm tra chữ ký trên body thô, trước khi parse JSON. Parse rồi stringify lại sẽ làm sai chữ ký.
  • Trả 2xx thật nhanh, xử lý nặng ở nền. Quá 10 giây bị tính là lỗi.
  • Chống trùng bằng id: cùng một sự kiện có thể đến nhiều lần do thử lại hoặc gửi lại.
  • Đừng dựa vào thứ tự đến. Các lần gửi chạy song song và lần thử lại có thể đến sau sự kiện mới hơn; dùng createdAt khi cần sắp xếp.
  • Trả 2xx cả cho sự kiện bạn cố ý bỏ qua. Mọi mã khác, kể cả 4xx, đều bị thử lại.

Thử lại và backoff

Lần gửi thành công khi endpoint trả mã 2xx. Mã khác, quá 10 giây, lỗi mạng hay địa chỉ bị từ chối đều là thất bại và được thử lại tự động:

  • Tối đa 15 lần gửi: lần đầu và 14 lần thử lại.
  • Khoảng cách nhân đôi sau mỗi lần lỗi: 1 phút, 2 phút, 4 phút, 8 phút… cho tới mức trần 12 giờ giữa hai lần. Tổng cộng khoảng 3 ngày.
  • Trong lúc còn lượt thử, nhật ký hiện trạng thái Đang chờ kèm lỗi gần nhất và số lần đã gửi. Trạng thái Lỗi chỉ xuất hiện sau lần thử cuối.

Tạm dừng sau lỗi liên tục

Khi một lần gửi thất bại ở lượt thử cuối cùng, endpoint bị tạm dừng: công tắc trên thẻ tắt đi, dòng "Đã tạm dừng vì lỗi liên tục, bật lại khi đích đã ổn" hiện lên, và mọi Chủ sở hữu và Quản trị của workspace nhận một email (hiện bằng tiếng Anh) có nút mở trang webhook.

Trong lúc tạm dừng:

  • Sự kiện mới không được ghi lại cho endpoint này, và cũng không gửi bù được về sau.
  • Những lần gửi còn đang chờ thử lại chuyển sang Lỗi với lỗi webhook_disabled.

Để chạy lại, sửa endpoint rồi bật công tắc trên thẻ. Bộ đếm lỗi được đặt về 0. Sau đó mở nhật ký và Gửi lại những lần gửi bị lỗi. Tự tắt công tắc cũng có tác dụng y như tạm dừng, chỉ là không có email.

Gửi thử

Bấm Gửi thử trên thẻ endpoint. Thông báo "Đã gửi sự kiện thử" nghĩa là sự kiện đã vào hàng đợi, chưa chắc đã đến nơi; kết quả nằm trong nhật ký sau vài giây. Sự kiện webhook.test đi qua đúng đường ký và ghi log như sự kiện thật, dù bạn có tick webhook.test hay không, với data là { "message": "Signed and delivered by ClavisAUH" }.

Nhật ký gửi và gửi lại

Mỗi thẻ endpoint hiện số lần gửi trong 24 giờ qua: "… thành công", "… lỗi", và "… đang chờ" khi có. Bấm Nhật ký gửi để mở bảng Lịch sử gửi của endpoint đó, tự làm mới mỗi 5 giây và hiện 50 lần gửi gần nhất. Mỗi dòng có tên sự kiện, trạng thái (Đang chờ, Thành công, Lỗi), mã HTTP, thời gian phản hồi, số lần đã gửi (×3) và thời điểm tạo.

Bấm vào một dòng để xem lỗi gần nhất, payload và tối đa 1.000 ký tự đầu của phản hồi từ server bạn. Nút Gửi lại tạo một lần gửi mới với đúng payload cũ (cùng id); thông báo "Đã xếp hàng gửi lại" hiện lên và dòng cũ vẫn giữ trong nhật ký. Nếu endpoint đang tạm dừng, hãy bật lại trước, nếu không lần gửi lại báo ngay webhook_disabled.

Lỗi trong nhật kýNguyên nhân
HTTP 500 (hoặc mã khác)Endpoint trả mã không phải 2xx
webhook_disabledEndpoint đang tắt hoặc tạm dừng lúc gửi
endpoint resolves to a private addressTên miền trỏ về địa chỉ nội bộ
only https endpoints are allowedURL không dùng https
unreadable_secretServer không giải mã được secret đã lưu; thử lại không giúp được, hãy bấm Đổi bí mật và cập nhật bên nhận

Nhật ký gửi được dọn theo thời gian lưu log của gói, trừ những lần gửi còn đang chờ.

Hạn mức theo gói

Số webhook được tính trên toàn workspace, cộng mọi ứng dụng. Theo mặc định, gói Basic cho 1 webhook và Premium cho 50; con số áp dụng cho bạn nằm ở Tài khoản & tổ chức › Gói & sử dụng. Khi ứng dụng đã đủ hạn mức, nút Thêm webhook bị khoá và trang hiện dòng "Gói hiện tại cho tối đa N webhook" với N là hạn mức của bạn. Nếu phần hạn mức còn lại đã được ứng dụng khác dùng, việc tạo bị từ chối với mã plan_limit (HTTP 402). Xem Gói Basic, Premium và hạn mức và bảng giá.

Quản lý qua Seller API

Khoá Seller API có quyền webhooks:read hoặc webhooks:write quản lý được webhook từ hệ thống của bạn:

  • GET /apps/{appId}/webhooks: liệt kê endpoint của ứng dụng.
  • POST /apps/{appId}/webhooks: tạo endpoint với url, events và description tuỳ chọn. Secret trả về một lần trong data.secret. Truyền "events": ["*"] để nhận mọi sự kiện, kể cả sự kiện thêm vào sau này; dashboard không có lựa chọn này.
  • DELETE /webhooks/{id}: xoá endpoint.
  • POST /webhooks/{id}/test: gửi webhook.test.

Đổi secret, bật tắt, xem nhật ký và gửi lại hiện chỉ làm trong dashboard. Hạn mức gói áp dụng như trên. Chi tiết trong tham chiếu Seller API.

Liên quan

Bạn cần giúp gì?

Câu hỏi thường gặp

ClavisAUH là gì?

Dịch vụ bản quyền và xác thực cho phần mềm desktop: license key, khoá thiết bị, đăng nhập người dùng và phản hồi có chữ ký. Tất cả quản lý trên một dashboard

Vì sao kẻ crack không làm giả được server?

SDK chỉ tin câu trả lời ký bằng khoá bí mật của ứng dụng. Khoá đó không bao giờ rời server của chúng tôi, nên server giả không ký được và bị từ chối

Trong file chạy của tôi có gì?

Mã ứng dụng và khoá công khai. Không có bí mật nào, vì mỗi thiết bị tự tạo cặp khoá riêng ở lần chạy đầu

Có chạy được khi offline không?

Có. Mọi gói đều có token offline có chữ ký dùng được 7 ngày, ứng dụng tự kiểm tra tại máy. Premium cho bạn chọn bao lâu. License đã thu hồi vẫn có thể chạy offline trong khoảng đó, trừ khi Chế độ chỉ online đang bật

Hỗ trợ những ngôn ngữ nào?

Hiện có JavaScript và TypeScript. Tiếp theo là C++, C# và Python, cùng một giao thức công khai

Gói Basic có miễn phí thật không?

Có. Không giới hạn thời gian, không cần thẻ. Cần nhiều hơn thì lên Premium

Liên hệ