集成文档

把密钥激活接进你的桌面软件:三步完成对接,支持在线激活、心跳校验、自助解绑与离线验签。

快速开始

整体只有三步:

  1. 联系我们申请一组接口密钥,拿到 key_id 与 secret(secret 只下发一次,请妥善保存)。
  2. 客户端采集设备指纹 device_id(HWID 的 SHA-256),连同激活码调用 /api/v1/activate。
  3. 把返回的 license_file 保存到本地,以后启动时先本地验签,再按 heartbeat_interval_hours 做在线心跳。
密钥属于敏感凭据:请只在客户端首次激活时使用,不要硬编码进可被反编译的位置; 如需轮换或停用,请随时联系我们。

接口鉴权(HMAC 签名)

每个请求需要携带 4 个请求头:

请求头说明
X-Yomur-KeyAPI Key 的 key_id,例如 yk_1a2b3c4d5e6f
X-Yomur-Timestamp毫秒时间戳;与服务端相差需在 ±5 分钟内
X-Yomur-Nonce随机串,10 分钟内不可重复(防重放)
X-Yomur-SignatureHMAC-SHA256 十六进制签名,见下

签名算法:

bodyHash = SHA256_HEX(rawBody)                    // 原始请求体字节的 sha256(GET 请求传空串)
base     = `${key_id}.${timestamp}.${nonce}.${bodyHash}`
signature = HMAC_SHA256_HEX(key = SHA256_HEX(secret), message = base)

服务端只保存 SHA256(secret),因此客户端计算签名时也用 SHA256(secret) 作为 HMAC 密钥 —— 这样平台无需存储明文密钥。

// Node.js 参考实现
const crypto = require('node:crypto');
function sign({ keyId, secret, body }) {
  const timestamp = Date.now().toString();
  const nonce = crypto.randomBytes(8).toString('hex');
  const raw = body ? JSON.stringify(body) : '';
  const bodyHash = crypto.createHash('sha256').update(raw).digest('hex');
  const base = `${keyId}.${timestamp}.${nonce}.${bodyHash}`;
  const hmacKey = crypto.createHash('sha256').update(secret).digest('hex');
  const signature = crypto.createHmac('sha256', hmacKey).update(base).digest('hex');
  return { timestamp, nonce, signature, raw };
}

POST /api/v1/activate — 激活设备

把当前设备绑定到激活码。首次激活占用一个设备额度;同一设备重复激活不会重复占用。

字段类型说明
license_keystring必填。用户输入的激活码
device_idstring必填。设备指纹(≥8 位,建议 32 位十六进制)
device_namestring选填。设备名,用于后台展示
osstring选填。操作系统版本
app_versionstring选填。客户端版本号
product_slugstring选填。用于校验激活码是否属于本产品
POST /api/v1/activate
Content-Type: application/json
X-Yomur-Key: yk_xxxxxxxxxxxx
X-Yomur-Timestamp: 1735689600000
X-Yomur-Nonce: 9f2c1a7b3d4e5f60
X-Yomur-Signature: <hmac>

{ "license_key": "YOMUR-3F9KA-8M2QV-XT7YB-5HNDP-9",
  "device_id": "A1B2C3D4E5F60718293A4B5C6D7E8F90",
  "device_name": "办公室台式机",
  "os": "Windows 11 23H2",
  "app_version": "1.0.0",
  "product_slug": "yomur-keygen-demo" }

成功响应(HTTP 200):

{
  "ok": true,
  "code": "ok",
  "message": "激活成功",
  "already_bound": false,
  "license": {
    "license_key": "YOMUR-3F9KA-8M2QV-XT7YB-5HNDP-9",
    "status": "active",
    "duration_days": 0,
    "max_devices": 2,
    "device_count": 1,
    "expires_at": null,
    "devices": [ { "device_id": "A1B2...", "device_name": "办公室台式机", "last_seen_at": "..." } ]
  },
  "license_file": "YOMUR-LIC-v1.eyJ2IjoxLCJrZXki...<签名>",
  "activation_token": "eyJsaWMiOjEsImRldiI6...",
  "offline_grace_days": 7,
  "heartbeat_interval_hours": 72,
  "server_time": "2025-01-01T00:00:00.000Z"
}

激活失败时返回 HTTP 400,body 中包含 code 与 message(见「错误码」)。

POST /api/v1/verify — 心跳 / 在线校验

建议每 heartbeat_interval_hours 小时调用一次,用于感知吊销、过期与解绑。

{ "license_key": "YOMUR-...", "device_id": "A1B2...", "app_version": "1.0.0" }
// 或使用激活时返回的令牌(无需再传 device_id)
{ "activation_token": "eyJsaWMiOjEsImRldiI6...", "app_version": "1.0.0" }

返回 { ok, code, status, license{...}, license_file };ok=false 且 code 为 revoked / expired / suspended / device_unbound 时,客户端应限制功能。

POST /api/v1/deactivate — 解绑当前设备

解绑后释放一个设备额度。是否允许自助解绑、以及解绑冷却时间,由产品设置决定(后台可配)。

{ "license_key": "YOMUR-...", "device_id": "A1B2..." }
// 或 { "activation_token": "eyJsaWMiOjEsImRldiI6..." }

错误码

codeHTTP含义与建议处理
ok200成功
bad_format400激活码格式/校验位错误 → 提示用户核对
not_found400激活码不存在 → 提示检查
device_limit400设备数达上限 → 引导到「激活与设备」页解绑
expired400授权过期 → 引导续费
revoked400已吊销(如退款)→ 停止使用
suspended400被暂停 → 联系客服
product_mismatch400激活码不属于当前产品
device_unbound400当前设备已被解绑 → 重新激活
device_not_activated400该设备尚未激活
bad_signature / timestamp_expired / nonce_replayed401签名或时间校验失败 → 检查密钥与系统时钟
rate_limited429请求过于频繁 → 退避重试

离线验签(Ed25519)

许可文件格式:YOMUR-LIC-v1.<payloadBase64Url>.<signatureBase64Url>

验签步骤:

  1. 用 /api/public-key 拿到 Ed25519 公钥(SPKI PEM),内置进客户端;
  2. 对 payload 的原始 JSON 字符串(UTF-8) 做 Ed25519 验签;
  3. 验签通过后,再校验 payload 中的 expires_at、max_devices 与本地缓存的在线校验时间;
  4. 超过 grace_days 未成功联网校验时,可降级为受限模式。
const crypto = require('node:crypto');
const [tag, dataB64, sigB64] = licenseFile.trim().split('.');
const payloadJson = Buffer.from(dataB64, 'base64url').toString('utf8');
const ok = crypto.verify(null, Buffer.from(payloadJson, 'utf8'),
                         crypto.createPublicKey(publicKeyPem),
                         Buffer.from(sigB64, 'base64url'));

网站端接口(下单 / 查询 / 下载)

接口说明
GET /api/site站点公开配置 + 产品列表
GET /api/products/:slug产品详情(含套餐与版本)
POST /api/orders创建订单并返回支付链接
GET /api/orders/:no?email=查询订单(需邮箱匹配)
POST /api/orders/:no/pay切换支付方式并重新生成支付单
GET /api/download/:token凭签名令牌下载安装包
GET /api/license/lookup激活码 + 邮箱查询授权与设备
POST /api/license/unbind自助解绑设备
GET /api/license/file下载 .lic 离线许可文件

需要 Node / 桌面端的完整参考实现(签名、激活、心跳、离线验签与设备指纹采集),请通过 客服邮箱 联系我们获取。