KKeyflow
首页 文档 卡密验证 状态 客户中心
控制台
目录接入流程认证方式激活试用心跳远程变量登出离线许可证签名公钥吊销列表版本接口Webhook错误码SDK
DEVELOPER GUIDE

客户端接入文档

Keyflow 的客户端协议是一组小型 JSON HTTP 接口。激活后获得会话令牌,通过心跳维持在线状态;需要完全离线运行时,可申请 Ed25519 签名许可证。

Base URL/api/v1所有请求与响应均为 application/json; charset=utf-8。请求体限制 64 KB。

接入流程

  1. 创建产品:在控制台设置产品编码(如 desktop-pro)、设备上限、心跳间隔与功能权益。
  2. 生成卡密:批量生成后通过你的渠道分发给用户。卡密明文只在生成时显示一次。
  3. 激活:客户端提交卡密与设备指纹,获得 session_token 与 features。
  4. 心跳:按 heartbeat_after_seconds 定时调用心跳,失败时降级或退出功能。
  5. 离线(可选):申请签名许可证,客户端用内置公钥离线校验。

认证方式

激活与离线许可证签发不需要令牌。心跳与登出使用激活返回的会话令牌:

Authorization: Bearer <session_token>

会话令牌仅以 SHA-256 摘要存储,服务端无法还原明文。请保存在操作系统安全存储中。

激活授权

POST /api/v1/activations

{
  "product_code": "desktop-pro",
  "card_key": "A1B2C-3D4E5-F6G7H-8J9K2",
  "device": {
    "fingerprint": "sha256-of-stable-device-id",
    "name": "Design Station",
    "os": "windows",
    "arch": "amd64",
    "client_version": "1.4.0"
  }
}

成功响应:

{
  "session_token": "9f2c…",
  "session_expires_at": "2026-10-24T12:00:00Z",
  "server_time": "2026-10-24T11:55:00Z",
  "heartbeat_after_seconds": 60,
  "remote_config": {
    "maintenance": false,
    "announcement": "v1.5 已发布"
  },
  "license": {
    "id": "lic_…",
    "product_code": "desktop-pro",
    "status": "active",
    "expires_at": "2026-11-23T11:55:00Z",
    "features": ["pro", "export", "api-access"]
  }
}
幂等重试Idempotency-Key网络超时后可用同一幂等键重试,服务端会返回完全相同的响应。相同键配合不同请求体会返回 409。

设备指纹建议

  • 使用稳定的硬件或安装标识,不要使用会随网络变化的 MAC 地址。
  • 对原始标识做 SHA-256 后再上传,客户端不应上传明文硬件信息。
  • 指纹变化会消耗一个新的设备名额,因此升级或重装时应尽量保持标识稳定。

设备试用

POST /api/v1/trials,不需要卡密。产品需在后台开启「允许试用」并配置试用天数。

{
  "product_code": "desktop-pro",
  "device": {
    "fingerprint": "sha256-of-stable-device-id",
    "name": "Design Station",
    "os": "windows",
    "arch": "amd64",
    "client_version": "1.4.0"
  }
}

成功响应与激活一致,并额外返回生成的试用卡密:

{
  "session_token": "9f2c…",
  "session_expires_at": "2026-10-27T11:55:00Z",
  "server_time": "2026-10-24T11:55:00Z",
  "heartbeat_after_seconds": 60,
  "trial": true,
  "card_key": "TRIAL-…",
  "remote_config": {},
  "license": { "type": "trial", "features": ["pro"], "expires_at": "2026-10-27T11:55:00Z" }
}
  • 同一产品的同一设备指纹只能领取一次,重复领取返回 409 TRIAL_ALREADY_CLAIMED。
  • 试用授权默认绑定 1 台设备,到期后自动失效,不占用正式卡密的设备名额。
  • 产品未开启试用返回 403 TRIAL_DISABLED。

心跳续期

POST /api/v1/sessions/heartbeat(Bearer 令牌)

{
  "status": "active",
  "session_expires_at": "2026-10-24T12:00:00Z",
  "server_time": "2026-10-24T11:59:00Z",
  "heartbeat_after_seconds": 60,
  "license_expires_at": "2026-11-23T11:55:00Z",
  "features": ["pro", "export", "api-access"],
  "remote_config": {
    "maintenance": false,
    "announcement": "v1.5 已发布"
  }
}

授权被冻结、吊销、到期或设备被解绑时,心跳会返回 403,客户端应立即锁定功能。网络失败可短暂宽限,但不应无限期离线运行。

远程变量

GET /api/v1/config(Bearer 令牌)可在不续期、不重新激活的情况下拉取最新配置。适合在设置页打开、切换功能模块或长连接空闲时调用。

{
  "status": "active",
  "server_time": "2026-10-24T11:59:30Z",
  "heartbeat_after_seconds": 60,
  "remote_config": { "maintenance": false, "max_export_rows": 5000 },
  "features": ["pro", "export"],
  "license": {
    "id": "lic_…",
    "type": "duration",
    "status": "active",
    "expires_at": "2026-11-23T11:55:00Z"
  }
}

远程变量在产品上配置默认值,单张授权可覆盖。合并规则:以产品默认值为底,授权覆盖层逐键覆盖;值为 null 表示删除该继承键。变量同时随激活、心跳、API 校验和本接口下发。

安全建议远程变量用于功能开关、公告、限流阈值等非敏感配置。不要下发密钥、数据库口令等机密,也不要仅依赖客户端开关做授权判断。

主动登出

POST /api/v1/sessions/logout(Bearer 令牌),成功返回 204 No Content。

离线许可证

POST /api/v1/offline-licenses(Bearer 令牌)。产品必须启用离线签名。

{
  "payload": "eyJzY2hlbWFfdmVyc2lvbiI6…",
  "signature": "5m3n…",
  "kid": "ed25519_ab12cd34ef56"
}

payload 是 Base64URL 编码的 JSON 声明,signature 是对原始 payload 字节的 Ed25519 签名。声明结构:

{
  "schema_version": "network-auth.license.v1",
  "grant_id": "grt_…",
  "license_id": "lic_…",
  "product_code": "desktop-pro",
  "product_name": "Desktop Pro",
  "device_hash": "sha256…",
  "features": ["pro", "export"],
  "issued_at": "2026-10-24T11:55:00Z",
  "not_before": "2026-10-24T11:55:00Z",
  "expires_at": "2026-11-23T11:55:00Z",
  "revocation_epoch": 3
}

客户端校验步骤

  1. Base64URL 解码 payload 与 signature。
  2. 用内置公钥验证签名,拒绝任何验证失败的载荷。
  3. 校验 device_hash 与当前设备一致。
  4. 校验 not_before / expires_at 与系统时间。
  5. 定期获取吊销列表,若许可证被吊销或 revocation_epoch 落后则拒绝。
当前签名密钥ed25519_c38e0dbb76bd通过 GET /api/v1/signing-keys 获取 Base64URL 编码的原始 Ed25519 公钥。

签名公钥

GET /api/v1/signing-keys

{
  "keys": [
    {
      "kid": "ed25519_ab12cd34ef56",
      "algorithm": "Ed25519",
      "public_key": "Base64URL-encoded-32-byte-key"
    }
  ]
}

将公钥编译进客户端。签名私钥只保存在服务端,不会通过网络传输。

吊销列表

GET /api/v1/revocations?product_code=desktop-pro

{
  "product_code": "desktop-pro",
  "revocation_epoch": 4,
  "revoked": [
    { "license_id": "lic_…", "revoked_at": "2026-10-20T08:30:00Z" }
  ],
  "server_time": "2026-10-24T11:55:00Z"
}

吊销授权会同时递增产品的 revocation_epoch。离线客户端可缓存该列表,并在联网时刷新。

版本接口

获取最新发布版本:

GET /api/v1/releases/latest?product_code=desktop-pro&channel=stable

列出全部已发布版本:

GET /api/v1/releases?product_code=desktop-pro

响应包含 version、download_url、checksum、mandatory、notes 与 published_at。下载后请自行校验 sha256。

Webhook

控制台创建 Webhook 后可订阅授权事件。每次投递包含以下请求头:

X-Keyflow-Event: license.activate
X-Keyflow-Delivery: whd_…
X-Keyflow-Signature: sha256=<hex hmac>
Content-Type: application/json

签名计算方式:HMAC-SHA256(secret, raw_request_body)。请使用常量时间比较,并校验时间戳/事件 ID 防止重放。

{
  "id": "whd_…",
  "event": "license.activate",
  "created_at": "2026-10-24T11:55:00Z",
  "data": {
    "license_id": "lic_…",
    "device_id": "dev_…",
    "session_id": "ses_…",
    "product_code": "desktop-pro"
  }
}

支持事件:license.activate、license.freeze、license.unfreeze、license.revoke、device.unbind、session.revoke、release.published、customer.register、customer.redeem,或 * 订阅全部。

错误码

HTTPcode含义建议处理
400INVALID_REQUEST字段缺失或格式错误检查请求体,不要自动重试
401SESSION_EXPIRED会话不存在或已过期重新激活
401SESSION_REVOKED会话被管理员或解绑操作撤销提示用户重新登录/激活
403LICENSE_UNAVAILABLE / REVOKED / FROZEN / EXPIRED授权不可用停止业务功能并提示原因
403DEVICE_LIMIT_REACHED设备数达到上限提示用户解绑旧设备
403DEVICE_BLOCKED设备被禁用联系管理员
403OFFLINE_DISABLED产品未启用离线许可证使用在线模式
403TRIAL_DISABLED产品未开启试用改用卡密激活
409TRIAL_ALREADY_CLAIMED该设备已领取过试用提示购买正式授权
409IDEMPOTENCY_CONFLICT幂等键复用于不同请求更换幂等键
429RATE_LIMITED请求过于频繁按 Retry-After 退避

SDK 快速开始

本仓库在 sdk/ 下提供 Go、TypeScript、C#/.NET 与 C++ 四套实现,接口、错误码和离线验签行为保持一致。完整安装说明见各目录的 README。

Go

client := keyflow.New(keyflow.Options{
    BaseURL:     "https://license.example.com",
    ProductCode: "desktop-pro",
})

session, err := client.Activate(ctx, keyflow.ActivationInput{
    CardKey:     card,
    Fingerprint: fingerprint,
    DeviceName:  hostname,
})
if err != nil {
    log.Fatal(err)
}
if session.HasFeature("export") {
    enableExport()
}

TypeScript

import { KeyflowClient } from '@keyflow/client';

const client = new KeyflowClient({
  baseUrl: 'https://license.example.com',
  productCode: 'desktop-pro',
});

const session = await client.activate({
  cardKey,
  fingerprint,
  deviceName: 'Design Station',
});

if (session.license.features.includes('export')) enableExport();

C# / .NET

var client = new KeyflowClient(new KeyflowOptions {
    BaseUrl = "https://license.example.com",
    ProductCode = "desktop-pro",
});

var session = await client.ActivateAsync(new ActivationRequest {
    CardKey = card,
    Fingerprint = fingerprint,
    DeviceName = Environment.MachineName,
});

C++

keyflow::Client client("https://license.example.com", "desktop-pro");

keyflow::ActivationInput input;
input.card_key = card;
input.fingerprint = fingerprint;
input.device_name = "Design Station";

auto session = client.activate(input);
if (session.has_feature("export")) enable_export();

试用与远程变量

各 SDK 均提供 StartTrial(无需卡密)与 Config(刷新远程变量)。会话对象会缓存最近一次下发的远程变量,心跳成功后自动更新。

// Go
trial, err := client.StartTrial(ctx, keyflow.ActivationInput{Fingerprint: fingerprint})
snapshot, err := trial.Session.RefreshConfig(ctx)
fmt.Println(trial.CardKey, snapshot.RemoteConfig["announcement"])

// TypeScript
const trial = await client.startTrial({ fingerprint });
const snapshot = await trial.session.refreshConfig();
console.log(trial.cardKey, trial.session.remoteConfig);

// C# / .NET
var trial = await client.StartTrialAsync(new TrialRequest { Fingerprint = fingerprint });
var snapshot = await trial.Session.RefreshConfigAsync();
Console.WriteLine(snapshot.RemoteConfig["announcement"]);

离线验签

从 /api/v1/signing-keys 获取公钥后按 kid 缓存,验证签名、时间窗口、device_hash 与吊销 epoch。生产环境可在构建时固定公钥,避免运行期替换。各 SDK 均提供 VerifyOfflineLicense 等价方法。

浏览器与 CORS

授权 API 默认不开放跨域。浏览器应用需在服务端设置 CORS_ALLOWED_ORIGINS(逗号分隔的完整 Origin),预检请求会自动放行 Authorization、Content-Type 与 Idempotency-Key。Electron 主进程、Node.js 与服务端接入无需 CORS。不要在前端页面中内置卡密。

初始化控制台查看服务状态
Keyflow轻量级软件授权与网络验证
首页状态卡密验证客户中心
ed25519_c38e0dbb76bd