เอกสารสำหรับนักพัฒนา

คู่มือการผสาน KeyThai License API เข้ากับซอฟต์แวร์ของคุณ — API base: https://api.keythai.net/v1

เริ่มต้นใช้งาน

  1. สร้างบัญชี — สมัครที่ /signup แล้วยืนยันอีเมล ระบบจะสร้าง tenant และ Ed25519 keypair ให้อัตโนมัติ
  2. สร้าง Product — ใน dashboard กำหนดชื่อ, code และแพลตฟอร์มของซอฟต์แวร์
  3. สร้าง Policy — เลือกประเภท (subscription / trial / floating / perpetual), จำนวนเครื่องสูงสุด (max_activations) และอนุญาต offline หรือไม่
  4. สร้าง License — ออก license key รูปแบบ KEYT-XXXX-XXXX-XXXX-XXXX-XXXX (key จะโชว์ครั้งเดียว ระบบเก็บเฉพาะ SHA-256 hash)
  5. รับ API Key — สร้าง API key (kt_live_...) ในหน้า API Keys เพื่อใช้เรียก License API

การยืนยันตัวตน

ทุก request ต้องแนบ API key ของ tenant ใน header แบบ Bearer token:

header
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 เดิม

request
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 (เลือกใส่ได้)

response 200
{
  "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 เลือกใส่ได้ ถ้าต้องการเช็คว่าเครื่องนี้ลงทะเบียนไว้)

request
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

request
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

request
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

request
curl https://api.keythai.net/v1/keys \
  -H "Authorization: Bearer kt_live_xxxxxxxxxxxx"
response 200
{
  "keys": [
    {
      "kid": "key-2024-01",
      "alg": "EdDSA",
      "publicJwk": {
        "kty": "OKP",
        "crv": "Ed25519",
        "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
      }
    }
  ]
}

Error Codes

เมื่อเกิดข้อผิดพลาด API จะตอบกลับในรูปแบบ { "error": { "code", "message" } }

error response
{
  "error": {
    "code": "SEAT_LIMIT_REACHED",
    "message": "เปิดใช้งานครบจำนวนเครื่องสูงสุดแล้ว"
  }
}
CodeHTTPคำอธิบาย
INVALID_KEY404ไม่พบ license key นี้ในระบบ
LICENSE_SUSPENDED403license ถูกระงับการใช้งานชั่วคราว
LICENSE_REVOKED403license ถูกเพิกถอนถาวร
LICENSE_EXPIRED403license หมดอายุแล้ว
SEAT_LIMIT_REACHED409เปิดใช้งานครบจำนวนเครื่องสูงสุดแล้ว
MACHINE_NOT_FOUND404ไม่พบเครื่องนี้ (fingerprint) ใน license
RATE_LIMITED429เรียก API ถี่เกินไป ลองใหม่ภายหลัง
QUOTA_EXCEEDED429ใช้โควตา API รายวันของแพ็กเกจหมดแล้ว
UNAUTHORIZED401API key ไม่ถูกต้องหรือไม่ได้แนบมา
VALIDATION_ERROR400request body ไม่ถูกต้องตามรูปแบบ

Device Fingerprint

fingerprint คือสตริงเฉพาะของแต่ละเครื่อง (ความยาว 8–255 ตัวอักษร) ใช้ผูก license กับเครื่อง แนะนำให้นำค่าเฉพาะของเครื่องมา hash ด้วย SHA-256 แล้วส่งเป็น hex string

  • Windows: ใช้ค่า MachineGuid จาก registry HKLM\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 ออกก่อนเซ็น

verify (WebCrypto)
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 ได้โดยไม่ต้องต่อเน็ต

offline .lic
// .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 }

webhook payload
{
  "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.activated
  • X-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

verify webhook (Node.js)
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 8sdk-examples/csharp/KeyThaiClient.cs (HttpClient + fingerprint จาก MachineGuid + verify Ed25519)
  • Node.jssdk-examples/node/keythai-client.mjs (fetch + WebCrypto offline verify)
  • Pythonsdk-examples/python/keythai_client.py (requests + cryptography/pynacl)