业务记录与证据管理工作台
全程留痕,有据可查
管理移动网页录制、关联业务信息,让记录检索与证据核验有序开展。
蜀企集团 · 可回溯可信系统
连接网页录制与业务记录,为日常管理、过程追溯和证据核验提供清晰的工作入口。
PLATFORM OVERVIEW
业务记录与证据管理工作台
管理移动网页录制、关联业务信息,让记录检索与证据核验有序开展。
RECENT SESSIONS
| 会话 | 项目 | 订单 | 状态 | 时间 |
|---|
INTEGRITY CHAIN
| 会话 ID | 项目 | 订单/手机号 | 时长 | 事件 | 保存状态 | 录制时间 |
|---|
每个项目独立管理来源策略和服务端凭证;多域名商户可以关闭域名白名单。
商户是平台的数据隔离边界,项目、会话和成员账号均归属于商户。
| 商户名称 | 唯一标识 | 项目数 | 管理员 | 保留/月配额 | 状态 | 创建时间 |
|---|
按角色控制查看、验证、导出和管理权限;新账号首次登录必须修改密码。
| 账号 | 显示名称 | 角色 | MFA | 状态 | 最后登录 |
|---|
从能运行的示例开始,按业务场景完成接入。适用平台 4.10.2 / SDK 4.4.0。
打开录制沙箱 ↗描述你卡在哪一步,可附不含敏感信息的错误码或示例。助手仅解释文档,不读取业务数据,也不执行录制或修改配置;回答请对照来源核实。
问答仅保留在当前页面内存,清空、刷新或退出登录后移除。清空本页对话不能撤回已经发送的内容。外部服务对内容的处理还受其服务协议和隐私政策约束,请查看 腾讯云 TokenHub 服务商说明 ↗;本平台不承诺服务商零留存。
从上方推荐问题开始,或用自己的话描述问题。
展开后检查服务状态。
从这里开始
每个下载包都包含后端、网页、配置模板与中文 README。只在商户后端填写项目凭证,网页会逐步给出中文建议和请求编号。
.env.example 复制为 .env,填写 KHS_PROJECT_CREDENTIAL 并按 README 启动。http://127.0.0.1:3080 打开页面;项目启用来源白名单时加入这个完整来源,不要混用 localhost。默认本机运行并使用合成资料。调试结果仅表示本地链路通过;正式验收需后端 KHS_DEMO_ALLOW_PRODUCTION=true 与页面双重确认,测试记录也可能进入 COS 锁定。示例 Cookie、CSRF 和本地存储不能直接代替生产身份认证、订单归属与录制告知。
先分清楚,再填写配置
cred_xxx.secret 只保存在商户后端;用于申请/续期、提交业务与查询。官方组件自动添加 Bearer。recordingToken 只给当前网页 SDK;绑定项目、来源与会话,不能用它代替服务端凭证。traceSessionId 是会话编号,不是授权凭据。商户后端要保存“业务流程 → 会话”的对应关系,检查当前用户、订单与会话归属。前端知道一个 ID,不代表可以操作它。
服务端环境变量只填凭证本体,不要包含 Bearer 或返回前端。手写认证头为 Authorization: Bearer <完整凭证>,中间一个普通空格。上线前先调用 GET /api/v1/credential-status。
最短接入路径
同一业务先保存 Key,再调用平台。同 Key、同参数始终对应同一会话,30 分钟 Token 缓存到期可续原会话,不会新建。
Key 按项目凭证隔离,不允许分配给其他业务;换凭证后仍须依赖后端原 ID。已结束返回 SESSION_ALREADY_FINISHED,已删除/不可恢复返回 410,不要换 Key 掩盖错误。
下面的原始 HTTP 只能运行在商户后端。ID 与 Key 都应来自已授权的业务流程;origin 必须与实际 H5 来源一致,并由后端可信配置核实。
POST /api/v1/recording-tokens
Authorization: Bearer <完整项目服务端凭证>
Content-Type: application/json
{
"origin": "https://h5.customer.example",
"flowRef": "flow-your-business-000001",
"idempotencyKey": "entry-your-business-000001"
}同 Key 换参数返回 IDEMPOTENCY_CONFLICT。重试复用原 Key;每凭证 5 分钟最多申请 300 次,429 按 Retry-After 退避。
只补续期请直接看第 05 节,不用重做已完成的流程。/business/recording-context 和 /business/recording-token 是商户自建接口的示例路径,不是平台接口,可替换为已有受业务身份/CSRF保护的接口。context 返回原会话 ID(首次为 null)和商户自己的 CSRF 值。
<script src="https://khs.szshuqi.com/sdk/recorder.min.js"
crossorigin="anonymous" defer></script>
<script>
window.addEventListener("DOMContentLoaded", async () => {
const status = document.querySelector("#recording-status");
const showStatus = message => { if (status) status.textContent = message; };
KHSRecorder.error(error => {
showStatus(`${error.code}: ${error.message};请求编号 ${error.requestId || "本地检查"}`);
});
try {
const response = await fetch("/business/recording-context", {
credentials: "same-origin", cache: "no-store"
});
const context = await response.json();
if (!response.ok || context.ok !== true) throw new Error("无法读取当前业务录制");
const tokenProvider = async ({ reason, traceSessionId, origin, handoffToken, signal }) => {
const result = await fetch("/business/recording-token", {
method: "POST", credentials: "same-origin",
headers: { "Content-Type": "application/json", "X-CSRF-Token": context.csrf },
body: JSON.stringify({ reason, traceSessionId, origin, handoffToken }),
signal
});
const data = await result.json();
if (!result.ok || data.ok !== true) throw Object.assign(new Error(data.message || "录制凭证请求失败"), {
code: data.code, requestId: data.requestId, retryAfter: data.retryAfter
});
return data;
};
await KHSRecorder.init({
endpoint: "https://khs.szshuqi.com",
tokenProvider,
...(context.traceSessionId ? { traceSessionId: context.traceSessionId } : {}),
pagehideStrategy: "flush"
});
showStatus("录制已开始");
} catch (error) {
showStatus(error.message || "录制启动未完成,请稍后重试");
}
});
</script>
<p id="recording-status" role="status" aria-live="polite"></p>动态 SDK 最多缓存 60 秒。回调参数由 SDK 自动提供;signal 传给 fetch,不放进 JSON。原会话 ID 在首屏 init 前传入,恢复还须满足第 05 节的条件。
订单可以在录制后生成
POST /api/v1/sessions/{traceSessionId}/commit 统一绑定订单、手机号与保存确认。至少提供一项;同一手机号可以绑定多个会话,同会话相同内容重试幂等。
// 商户后端:先核实当前用户、录制与订单的归属
await khs.commitSession(traceSessionId, {
businessId: "ORDER-DEMO-001",
mobile: "+999000000001", // 合成示例号码
confirmed: true
});
// 也可先绑定号码,稍后再补订单与确认
await khs.commitSession(traceSessionId, { mobile });
await khs.commitSession(traceSessionId, {
businessId, confirmed: true
});
后台原名称“全部永久保存”。正式会话按项目保留和锁定策略直接进入腾讯云,无需额外确认;仍可传 confirmed:true。
录制先进入本地待确认区。后端须在会话首次签发(创建)后 72 小时内提交 confirmed:true,录制结束后再异步转存;72小时未确认自动删除。
不能由 H5 直接调用。confirmed 表示商户确认保存,不是用户同意录制;真实订单与手机号须核实归属。
commit 成功不等于存证完成。判断会话策略用 confirmationRequired / confirmationDeadline;期限以 confirmationDeadline 为准,续期不会重算截止时间,也不从订单生成时间起算。
同一手机号允许绑定多个会话;号码会移除空格、括号和连字符,可带国际区号 +。若已绑定不同订单,先核实原因,再通过 POST /api/v1/sessions/{traceSessionId}/bindings/revoke 追加撤销记录,之后 commit 新订单。撤销不删除原记录。调试会话提交仍为 debug_local,不会因此进入腾讯云;切换正式也不会补传旧调试数据。
只补续期,看这里即可
SDK 调用 tokenProvider → 商户后端请求以下接口 → 返回成功 JSON → SDK 自动更新 Token,继续原会话。
POST https://khs.szshuqi.com/api/v1/sessions/{traceSessionId}/renew-token
Authorization: Bearer <项目服务端凭证>
Content-Type: application/json
{}
traceSessionId:首次签发返回的原会话 ID,续期时 SDK 也会自动提供;后端须验证它属于当前用户或业务流程。{},不需要旧录制 Token、订单号、手机号或其他参数。成功返回包含 ok:true、原 traceSessionId、新 recordingToken、expiresIn 和 requestId。后端把成功 JSON 返回网页,tokenProvider 返回该对象;若包装成 {data:…},回调返回里面的 data。不用重新 init 或新建会话。
以下参数由 SDK 自动提供;续期仅用 traceSessionId 拼接平台请求路径,其余不是平台续期接口必填参数。
| 参数 | 来源与用途 |
|---|---|
| traceSessionId | SDK 提供原会话 ID;后端用于续期 URL |
| reason | SDK 提供调用原因;统一后端接口用它区分首次、续期或跨域 |
| origin | SDK 读取 window.location.origin;首次签发/跨域时核实来源,续期不用传 |
| handoffToken | SDK 从跨域链接读取的接续凭证;普通续期不用传 |
| signal | SDK 的请求取消信号;放在 fetch 选项,不放进 JSON |
示例里的 context.csrf 来自商户自己的防跨站请求机制,不由平台签发;沿用现有业务身份和 CSRF 保护即可。
配置 tokenProvider 后由 SDK 自动触发续期,不要在错误监听里另起续期循环。Provider 支持下表全部场景;仅实现续期的回调不能直接用于首次或跨域入口。旧接入也可由后端取新 Token 后调用 KHSRecorder.updateToken(newRecordingToken),两种方式选一种。
失败看 code / message / requestId:INVALID_CREDENTIAL 检查后端凭证,TOKEN_NOT_RENEWED 检查是否返回相同旧 Token 或已过期 Token;SESSION_ALREADY_FINISHED 或不可恢复的 410 应停止续期,不要循环重试或偷偷新建会话。
| 原因/场景 | 商户后端负责 | 网页 SDK 负责 |
|---|---|---|
| initial | 校验身份和来源;同业务复用已有关联,首次用稳定 Key 签发 | 首次无 ID;不要每次渲染重复 init |
| resume | 校验原 traceSessionId 归属,再调用 renew-token | 首屏先读取后端原 ID,传给 init/resume |
| expiring / expired / rejected | 对原 ID 续期,返回新录制 Token | 自动替换并继续上传,无需重新初始化 |
| handoff | 核实原业务与目标来源,调用 handoffs/redeem 并保存子会话 | 目标页兑换关联片段,不能当成普通续期 |
| SPA / 新业务 | 同流程保留关联;真正新业务才生成新 Key | 同 document 路由通常继续录制,业务结束才 stop |
刷新续传须在离页前配置 pagehideStrategy:"flush"(默认 finish),业务结束才 stop。恢复还需原会话仍可恢复、Token 与当前页面来源匹配、本地缓存可用;仅传原 ID 不保证待传数据完整恢复。默认中断恢复窗口为 24 小时,是否可恢复以平台响应为准。
源页用 KHSRecorder.handoff(nextUrl, createHandoff) 等待当前片段完成后再跳转。createHandoff 调商户受保护后端,再由后端调用 /api/v1/sessions/{sessionId}/handoffs。目标页 Provider 的 handoff 分支调用 /api/v1/handoffs/redeem;initial 签发,resume/过期等续原 ID。
接续凭证有效期 5 分钟,只对指定项目和目标来源有效;重复兑换复用子会话。新片段以 journeyId/parentSessionId 关联,单独计额度、绑定订单并执行自身 72 小时确认策略。Token 不能直接跨域复用;后端仍须校验跳转和业务归属。hash 路由需测试 khs_handoff fragment 兼容性。
成功状态要说清楚
SDK 4.4.0 推荐监听 khs:finished。stop 成功与 getStatus().recordingStatus="finished" 表示网页录制上传结束;等待确认和转存仍由后端继续处理。旧公开字段 status 在结束时仍为 sealed,原商户轮询代码继续兼容;新代码用 recordingComplete 或 recordingStatus 判断上传结束。
| evidenceStatus | 正确显示 | 下一步 |
|---|---|---|
| ready | 平台返回正式存证完成 | 后端按需查询与核验 |
| awaiting_confirmation | 录制已上传,等待确认 | 后端在截止前 commit |
| archiving | 录制已上传,正在转存 | 等待任务/通知,不重建 |
| debug_complete | 本地调试封存完成 | 不是正式 COS 存证 |
| unknown | 已上传,保存状态待查询 | 缺字段不能猜测成功 |
window.addEventListener("khs:finished", event => {
const { evidenceStatus } = event.detail;
const labels = {
ready: "平台已返回正式存证完成,请由后端核对最终结果",
awaiting_confirmation: "录制已上传,等待商户确认保存",
archiving: "录制已上传,平台正在转存",
debug_complete: "调试录制已封存,仅本地临时保存",
unknown: "录制已上传,请查询最终保存状态"
};
showStatus(labels[evidenceStatus] || labels.unknown);
});
async function finishBeforeNavigation(nextUrl) {
try {
await KHSRecorder.stop();
// 是否还要等待正式存证,由业务后端的完成要求决定。
location.assign(nextUrl);
} catch (error) {
const current = KHSRecorder.getStatus();
showStatus(`尚有 ${current.pendingChunks} 个分块待传,请恢复网络后重试`);
throw error;
}
}返回值还包含 recordingComplete:true 和 persistenceState,getStatus 暴露 serverStatus。ready 需要正式环境、permanent、已就绪状态和 Manifest;旧 khs:sealed 仅为兼容别名,标记 canonicalEvent:khs:finished,不要双重监听执行业务。
跳转前显式等待 stop;页面关闭/强杀不能保证异步上传完成。NETWORK_OFFLINE / FINISH_DEFERRED 时保留原会话和 IndexedDB,显示待传分块数并联网重试。后端保留原 ID 直到最终结果;若业务必须取得正式存证才成功,还要等待后端确认。
业务后端的完成依据
GET /api/v1/sessions/{traceSessionId}/integration-status 只允许当前项目。Node 使用 getSessionStatus(id,{verify,resourceHash});PHP/Laravel 使用 getSessionStatus(id,verify,resourceHash);Java 同名重载。
stage=ready 才是正式 COS 存证;awaiting_confirmation 是待确认,archiving 是转存中,debug_complete 仅本地调试。incomplete/expired/failed 不能显示成功。
verification=null 表示本次未核验。verify=1 只读检查;正式自检还应检查 contentValid、wormProtected、cloudTimeVerified 和预期资源/绑定。
普通查询每凭证每分钟 120 次,verify=1 最多 6 次;超 200 分块、100 资源或 32MiB 到后台异步核验。轮询应退避并设停止条件。
正常封存关注 recording.sealed,确认后转存关注 recording.persisted;通知可能重试,也可能属于调试环境。先核对签名、环境与会话归属,再查询最终结果。
X-KHS-Signature: sha256=…;HMAC-SHA256 的输入必须是原始字节,不能 JSON 解析后重新序列化。event.id 建数据库唯一键。X-KHS-Delivery 只用于排错;通知没有独立签名时间戳头,不要误拒延迟重试。// Node 官方客户端;rawBody 是尚未经过 JSON 解析的原始正文
const { handleWebhook } = require("./sdk"); // 下载包内的正式服务端组件
await handleWebhook(
rawBody,
signatureHeader, // X-KHS-Signature
process.env.KHS_WEBHOOK_SECRET,
async (eventId, event) => {
// 商户必须实现:数据库唯一事件ID + 业务操作在同一事务
// 已处理事件幂等成功;处理失败回滚并抛错
return businessInbox.processOnce(eventId, event);
}
);
// 业务已落库或可靠入队后,HTTP才返回2xx;失败返回非2xx。verifyWebhook 返回已验签事件;handleWebhook 只负责验签并调用 processOnce,不内置业务数据库。businessInbox 是商户必须实现的事务适配器,不能用内存 Set 代替;重复已处理事件直接成功。Express 原始正文路由要放在 express.json() 前。
按错误定位到责任边界
| 错误码/现象 | 先检查什么 | 怎么处理 |
|---|---|---|
| INVALID_CREDENTIAL | 后端读取的完整凭证是否一致 | 调用 credential-status;不要在网页补凭证 |
| ORIGIN_NOT_ALLOWED / ORIGIN_MISMATCH | 协议、域名、端口与项目来源 | 对照 actualOrigin/expectedOrigin,不关闭 Token 来源绑定 |
| INVALID_RECORDING_TOKEN / TOKEN_PROVIDER_INVALID_TOKEN | Provider 是否误返回项目凭证或缺字段 | 返回原会话的新录制 Token |
| TOKEN_NOT_RENEWED | 是否返回相同旧 Token 或已过期 Token | 调用原 ID 的 renew-token,返回新且未过期的 Token |
| IDEMPOTENCY_CONFLICT | 同 Key 的参数是否改变 | 恢复原参数,保留业务关联 |
| SESSION_ALREADY_FINISHED / SESSION_ALREADY_SEALED | 会话是否已结束 | 查询原结果;真正新业务才创建新 Key |
| 410 / SESSION_EXPIRED | 72 小时确认期限、恢复窗口、调试保留期 | 停止不可恢复请求,不伪造续传 |
| NETWORK_OFFLINE / FINISH_DEFERRED | 离线、待传队列、等待预算 | 保留原 ID,恢复网络再 stop |
| 429 | 限流还是额度不足 | 按 code 处理,Retry-After / retryAfter 单位为秒 |
| SDK_UPGRADE_REQUIRED | 固定版本低于项目要求 | 升级引用,不覆盖旧固定文件 |
// SDK 已加载后、init()/resume() 之前注册
const cancelError = KHSRecorder.error(error => {
showStatus(`${error.code}: ${error.message};请求编号 ${error.requestId || "本地检查"}`);
// 如需上报,仅选取必要字段,不能打印完整 Token 响应
reportSafeError({
code: error.code, requestId: error.requestId,
stage: error.stage, status: error.status
});
});
// 仅联调时按需查看诊断
const cancelDebug = KHSRecorder.debug(info => showDiagnostics(info));
const diagnostics = KHSRecorder.getDiagnostics();
// SPA组件销毁时:cancelError(); cancelDebug();showStatus/reportSafeError/showDiagnostics 是商户自己的 UI/日志接线函数。KHSRecorder.error 不会代替 Promise 错误处理,init/stop 仍须 try/catch;getDiagnostics 用于安全诊断,不意味着录制输入已脱敏。
反馈问题时附错误码、请求编号、会话 ID、时间、具体步骤、SDK 版本和网络环境。不要把凭证、Token、Cookie、密码或完整错误对象上传到不受控的第三方日志平台。
完成技术联调后仍需落实业务控制
商户 confirmed 只确认保存,不是用户录制同意。示例中的本地浏览器身份不是生产授权;这些技术措施不能据此宣称所有商户或全平台已完成合规审查。
按需要展开
pagehideStrategy:"flush" 适合刷新续传;兼容默认 finish 会结束片段,真正业务结束须显式 stop。默认恢复窗口 24 小时。SDK 监听网络变化并按真实上传调整;khs:offline、khs:retry、khs:network-profile-changed、khs:recovered 用于提示,khs:finish-waiting-network 用于展示待传数量。
IndexedDB 按需读取分块,默认本地待传上限随档位约 32/24/16MB。khs:local-cache-full 时暂停并提示联网,不清库重建。durable=false 表示内存降级,不能保证刷新恢复。草稿约每 150~250ms 落盘并响应 freeze/隐藏,强杀仍是尽力保存。stop 默认预算 45/60/75 秒,超时保留队列重试。
“录制来源”与“资源归档来源”是两套策略。允许不限制第三方资源归档域名,不代表绕过浏览器 CORS、混合内容、第三方权限或大小限制;跨域不带 Cookie/Authorization,后端不代抓任意 URL。默认单文件 2MB、单会话 40 个/12MB,弱网会降低预算,stop 优先事件并取消未完成的可选抓取。关键图片提前加载,再用内容哈希查询确认归档。
Canvas/跨域 iframe 默认关闭,按实际需要启用并在真机评估性能;Canvas 约 10fps,不是原生屏幕录像。合作 iframe 必须接入同版本 SDK,用 initFrameBridge 与 allowedParentOrigins 授权父来源,不能绕过未合作页面的同源限制。
CSP 放行平台 script-src/connect-src,Worker 需要 worker-src blob:;不可用时回退主线程。动态 /sdk/recorder.min.js 最多缓存 60 秒自动获得兼容更新;固定 /sdk/recorder-4.4.0.min.js 一年 immutable,适合固定哈希,旧固定版本不会被静默覆盖。推荐 4.4.0,调试最低版本仍为 4.3.3,旧接入继续兼容。
调试会话的分块、资源、Manifest与日志只存服务器本地,默认 7 天,可设 1–30 天;不生成正式核验码或正式 PDF/ZIP/MP4,切正式不补传历史调试数据。Token 过期后 6 小时清理从未开始、无绑定/证据的空调试会话;其它记录按会话期限。
debug/getDiagnostics 是浏览器诊断,项目调试模式会额外保存 console 和异常上下文;按需用 KHSRecorder.log 或后端 debug-logs 补充必要信息。日志脱敏只是兜底,页面录制输入仍需商户自行排除不应采集字段;不能据日志脱敏就认为页面已脱敏。
PDF 方便阅读结论,ZIP 包含原始对象、签名、哈希与追加账本,MP4 是衍生视频,不能替代原始证据。官网核验码只核对完整性,不开放订单/手机号/回放。哈希与签名核验通过说明平台接收并封存的数据保持一致,不单独证明现实身份或未采集行为,也不保证司法必然采信。COS 时间用于核对云端存储,不是用户每次操作的精确时间或独立第三方签名时间戳。
| 时间 | 操作 | 操作人 | 资源 | 记录哈希 |
|---|
SECURITY EVENTS
| 时间 | 风险 | 事件 | 账号 | IP |
|---|
监控已接收会话的完整率、恢复与上传异常;任务失败可重试,导出完成后可下载(保留 7 天)。
| 分组 | 总会话 | 完整封存/已启动 | 录制中 | 中断 | 上传错误/重复/恢复 | 存储 |
|---|
| 客户 | 月会话/配额 | 累计存储/配额 |
|---|
| 任务 | 类型 | 状态 | 尝试 | 时间 | 结果/操作 |
|---|