เอกสารสำหรับนักพัฒนา
คู่มือการผสาน KeyThai License API เข้ากับซอฟต์แวร์ของคุณ — API base: https://api.keythai.net/v1
เริ่มต้นใช้งาน
- สร้างบัญชี — สมัครที่ /signup แล้วยืนยันอีเมล ระบบจะสร้าง tenant และ Ed25519 keypair ให้อัตโนมัติ
- สร้าง Product — ใน dashboard กำหนดชื่อ, code และแพลตฟอร์มของซอฟต์แวร์
- สร้าง Policy — เลือกประเภท (subscription / trial / floating / perpetual), จำนวนเครื่องสูงสุด (max_activations) และอนุญาต offline หรือไม่
- สร้าง License — ออก license key รูปแบบ
KEYT-XXXX-XXXX-XXXX-XXXX-XXXX(key จะโชว์ครั้งเดียว ระบบเก็บเฉพาะ SHA-256 hash) - รับ API Key — สร้าง API key (
kt_live_...) ในหน้า API Keys เพื่อใช้เรียก License API
การยืนยันตัวตน
ทุก request ต้องแนบ API key ของ tenant ใน header แบบ Bearer token:
Authorization: Bearer kt_live_xxxxxxxxxxxxหาก API key ไม่ถูกต้องหรือไม่ได้แนบมา จะได้รับ 401 UNAUTHORIZED โดยมีการจำกัด rate limit และโควตา API รายวันตามแพ็กเกจ
API Reference
endpoint ของ License API ทั้งหมดอยู่ภายใต้ https://api.keythai.net/v1 โดย {key} คือ license key เต็ม
POST /v1/licenses/{key}/activate
ลงทะเบียน fingerprint ของเครื่อง (กินที่นั่ง 1 seat) — idempotent ต่อ fingerprint เดิม
curl -X POST https://api.keythai.net/v1/licenses/KEYT-AB12-3C4D-5E6F-7G8H-9J0K/activate \
-H "Authorization: Bearer kt_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"fingerprint": "a1b2c3d4e5f6...",
"name": "เครื่องของสมชาย",
"platform": "windows",
"hostname": "DESKTOP-1234"
}'Request body: fingerprint (จำเป็น), name, platform, hostname (เลือกใส่ได้)
{
"valid": true,
"status": "active",
"expires_at": 1767225600,
"machine_count": 1,
"max_activations": 3,
"policy": { "type": "subscription", "offline_allowed": true },
"issued_at": 1717286400,
"nonce": "8f3a...",
"signature": "base64-ed25519-signature..."
}ทุก field ยกเว้น signature คือ payload ที่ถูกเซ็น (ดูหัวข้อ “ตรวจลายเซ็น”) หากเกินจำนวนเครื่อง จะได้ 409 SEAT_LIMIT_REACHED
POST /v1/licenses/{key}/validate
ตรวจสอบสถานะ / วันหมดอายุ / fingerprint — ตอบกลับ payload พร้อมลายเซ็นเหมือน activate (fingerprint เลือกใส่ได้ ถ้าต้องการเช็คว่าเครื่องนี้ลงทะเบียนไว้)
curl -X POST https://api.keythai.net/v1/licenses/KEYT-AB12-3C4D-5E6F-7G8H-9J0K/validate \
-H "Authorization: Bearer kt_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "fingerprint": "a1b2c3d4e5f6..." }'POST /v1/licenses/{key}/deactivate
คืนที่นั่ง โดยลบ fingerprint ที่ระบุออกจาก license
curl -X POST https://api.keythai.net/v1/licenses/KEYT-AB12-3C4D-5E6F-7G8H-9J0K/deactivate \
-H "Authorization: Bearer kt_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "fingerprint": "a1b2c3d4e5f6..." }'POST /v1/licenses/{key}/heartbeat
อัปเดต liveness ของเครื่อง (สำหรับ floating license) — ควรเรียกเป็นระยะตาม heartbeat_interval
curl -X POST https://api.keythai.net/v1/licenses/KEYT-AB12-3C4D-5E6F-7G8H-9J0K/heartbeat \
-H "Authorization: Bearer kt_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "fingerprint": "a1b2c3d4e5f6..." }'GET /v1/keys
ดึง public JWK (Ed25519) สำหรับนำไป verify ลายเซ็นแบบ offline
curl https://api.keythai.net/v1/keys \
-H "Authorization: Bearer kt_live_xxxxxxxxxxxx"{
"keys": [
{
"kid": "key-2024-01",
"alg": "EdDSA",
"publicJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
}
}
]
}Error Codes
เมื่อเกิดข้อผิดพลาด API จะตอบกลับในรูปแบบ { "error": { "code", "message" } }
{
"error": {
"code": "SEAT_LIMIT_REACHED",
"message": "เปิดใช้งานครบจำนวนเครื่องสูงสุดแล้ว"
}
}| Code | HTTP | คำอธิบาย |
|---|---|---|
| INVALID_KEY | 404 | ไม่พบ license key นี้ในระบบ |
| LICENSE_SUSPENDED | 403 | license ถูกระงับการใช้งานชั่วคราว |
| LICENSE_REVOKED | 403 | license ถูกเพิกถอนถาวร |
| LICENSE_EXPIRED | 403 | license หมดอายุแล้ว |
| SEAT_LIMIT_REACHED | 409 | เปิดใช้งานครบจำนวนเครื่องสูงสุดแล้ว |
| MACHINE_NOT_FOUND | 404 | ไม่พบเครื่องนี้ (fingerprint) ใน license |
| RATE_LIMITED | 429 | เรียก API ถี่เกินไป ลองใหม่ภายหลัง |
| QUOTA_EXCEEDED | 429 | ใช้โควตา API รายวันของแพ็กเกจหมดแล้ว |
| UNAUTHORIZED | 401 | API key ไม่ถูกต้องหรือไม่ได้แนบมา |
| VALIDATION_ERROR | 400 | request body ไม่ถูกต้องตามรูปแบบ |
Device Fingerprint
fingerprint คือสตริงเฉพาะของแต่ละเครื่อง (ความยาว 8–255 ตัวอักษร) ใช้ผูก license กับเครื่อง แนะนำให้นำค่าเฉพาะของเครื่องมา hash ด้วย SHA-256 แล้วส่งเป็น hex string
- Windows: ใช้ค่า
MachineGuidจาก registryHKLM\SOFTWARE\Microsoft\Cryptographyแล้ว hash SHA-256 - macOS: ใช้
IOPlatformUUID(จาก ioreg) แล้ว hash - Linux: ใช้
/etc/machine-idหรือ/var/lib/dbus/machine-id - ทั่วไป / Node: รวม hostname + MAC address แล้ว hash SHA-256 เป็น fallback
สำคัญ: ควรให้ค่า fingerprint คงที่ตลอดอายุการใช้งานบนเครื่องเดียวกัน เพื่อให้ activate เป็น idempotent (ไม่กินที่นั่งเพิ่ม)
ตรวจลายเซ็น Ed25519 & ไฟล์ Offline .lic
ทุก response ของ activate / validate มี field signature ซึ่งเป็นลายเซ็น Ed25519 (base64) เซ็นทับ canonical JSON ของ payload (เรียง key ตามตัวอักษร, ตัด field ที่เป็น undefined ออก) โดยตัด field signature ออกก่อนเซ็น
import { canonicalJson } from "@keythai/shared"; // หรือคัดลอกฟังก์ชันลงในแอป
// payload = response โดยตัด field "signature" ออก
const { signature, ...payload } = res;
const message = new TextEncoder().encode(canonicalJson(payload));
const sig = Uint8Array.from(atob(signature), (c) => c.charCodeAt(0));
const key = await crypto.subtle.importKey(
"jwk", publicJwk, { name: "Ed25519" }, false, ["verify"],
);
const ok = await crypto.subtle.verify({ name: "Ed25519" }, key, sig, message);ไฟล์ .lic แบบ offline: export จาก dashboard ได้เป็น base64 ของ { payload, signature, kid } แอป desktop ที่ฝัง public key ไว้สามารถ verify ได้โดยไม่ต้องต่อเน็ต
// .lic = base64( JSON.stringify({ payload, signature, kid }) )
const decoded = JSON.parse(atob(licFileContents));
// decoded.payload คือ SignedLicensePayload
// 1) verify decoded.signature เทียบกับ canonicalJson(decoded.payload) + public key ที่ฝังไว้
// 2) ตรวจ payload.expires_at เทียบเวลาปัจจุบัน
// 3) ตรวจ fingerprint ของเครื่องตรงกับที่อนุญาตหรือไม่Webhooks
KeyThai สามารถยิง webhook ไปยัง endpoint ของคุณเมื่อเกิดเหตุการณ์กับ license ตั้งค่า endpoint และเลือกประเภทเหตุการณ์ที่ต้องการได้ที่ /dashboard/webhooks ระบบจะสร้าง signing secret (kt_whsec_...) ให้ครั้งเดียวตอนสร้าง endpoint — เก็บไว้ให้ดีเพื่อใช้ตรวจสอบลายเซ็น
ประเภทเหตุการณ์ (Event Types)
license.created— สร้าง license ใหม่license.activated— เปิดใช้งานเครื่อง (กิน seat)license.deactivated— คืน seat ของเครื่องlicense.suspended— ระงับ license ชั่วคราวlicense.revoked— เพิกถอน license ถาวรlicense.expired— license หมดอายุ
รูปแบบ Payload
ทุก request เป็น POST พร้อม body เป็น JSON ในรูปแบบ { id, event, created_at, data }
{
"id": "whd_01J...",
"event": "license.activated",
"created_at": 1717286400,
"data": {
"license": {
"id": "lic_01J...",
"key_prefix": "KEYT-AB12",
"status": "active",
"product_id": "prd_01J...",
"expires_at": 1767225600
}
}
}Headers
X-KeyThai-Event— ประเภทเหตุการณ์ เช่นlicense.activatedX-KeyThai-Delivery— id ของการส่งครั้งนี้ (ใช้ทำ idempotency ฝั่งคุณ)X-KeyThai-Signature— ลายเซ็น HMAC ในรูปแบบsha256=<hex>คำนวณจาก raw body ด้วย secret ของ endpoint
การตรวจสอบลายเซ็น (Node.js)
ใช้ raw request body (ก่อน parse เป็น JSON) คำนวณ HMAC-SHA256 ด้วย secret ของ endpoint แล้วเทียบกับค่าใน header X-KeyThai-Signature ด้วยการเทียบแบบ timing-safe เพื่อกัน timing attack
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyKeyThaiWebhook(rawBody, signatureHeader, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}นโยบายลองส่งซ้ำ (Retry)
endpoint ของคุณควรตอบกลับสถานะ 2xx ภายใน 10 วินาที หากล้มเหลว ระบบจะลองส่งซ้ำตามช่วงเวลา 1 นาที → 10 นาที → 1 ชั่วโมง → 6 ชั่วโมง สูงสุด 5 ครั้ง หลังจากนั้นจะถือว่าการส่งล้มเหลวถาวร
ตัวอย่าง SDK
เรามีตัวอย่าง client พร้อมใช้งานในโฟลเดอร์ sdk-examples สำหรับภาษายอดนิยม:
- C# / .NET 8 —
sdk-examples/csharp/KeyThaiClient.cs(HttpClient + fingerprint จาก MachineGuid + verify Ed25519) - Node.js —
sdk-examples/node/keythai-client.mjs(fetch + WebCrypto offline verify) - Python —
sdk-examples/python/keythai_client.py(requests + cryptography/pynacl)