simple-verify
DOCUMENTATION 进入用户后台

使用文档

目录
  1. 快速开始
  2. 配置反向代理(国内站点必读)
  3. 前端 widget
  4. 服务端校验接口
  5. 错误码
  6. 服务端 SDK
  7. 调试凭据

1. 快速开始

第一步:拿凭据。用户后台注册并新建一个 key,得到一对:

国内站点请先配反向代理。 校验服务部署在 Cloudflare 上,国内访客直连的速度和稳定性都不理想。 把 /sv/ 代理到校验服务后,访客只与你自己的域名通信,加载明显更快, 也不会被广告拦截插件误伤。配置见下一节,只有一段 nginx。 下面的示例默认采用代理路径。

第二步:前端加两行。

你的页面(代理模式,推荐)
<script src="/sv/v0/api.js" async defer></script>

<form action="/register" method="post">
  <input name="email">
  <div class="sv-widget" data-appid="你的 appid"></div>
  <button type="submit">注册</button>
</form>

脚本会自动扫描 .sv-widget,在后台完成校验,并把令牌写进表单里一个名为 sv-response 的隐藏字段。用户无需任何点击。

没有自己的服务器(纯静态站、Vercel 等)?把两处路径换成绝对地址 https://verify-api.loadren.com 即可直接用,跨域已放行—— 只是国内访客的首次加载会慢一些。

第三步:后端校验令牌。表单提交后,用 appsecret 调一次校验接口:

curl
curl -X POST https://verify-api.loadren.com/api/siteverify \
  -d "secret=你的 appsecret" \
  -d "response=表单里的 sv-response" \
  -d "remoteip=访客 IP"

{"success":true,"challenge_ts":"2026-09-22T00:00:00Z","hostname":"your.site","score":90,"error-codes":[]}

successtrue 才继续处理业务。用服务端 SDK 可以省掉手写这段请求。

2. 配置反向代理(国内站点必读)

校验服务托管在 Cloudflare 上。国内访客直连境外节点,api.js 的首次加载 可能要几百毫秒到数秒,网络波动时还会失败。让访客只访问你自己的域名、 由你的服务器转发,可以显著改善这一点:

代理模式(推荐)直连模式
国内加载速度走你自己的服务器与 CDN,快取决于访客到 Cloudflare 的链路
广告拦截插件同源请求,不受影响第三方域名,可能被拦
跨域依赖 CORS(已放行)
接入成本一段 nginx 配置零配置
nginx 配置片段
location /sv/ {
    proxy_pass https://verify-api.loadren.com/;
    proxy_http_version 1.1;
    proxy_set_header Connection "";          # 复用长连接,省掉每次 TLS 握手
    proxy_set_header Host verify-api.loadren.com;
    proxy_ssl_server_name on;

    # 转发真实访客 IP —— 令牌的 IP 绑定依赖它
    proxy_set_header X-SV-Client-IP $remote_addr;

    proxy_connect_timeout 3s;
    proxy_read_timeout 10s;
}

建议再给 api.js 加一层缓存。脚本带 ETag,源站 max-age 为 5 分钟;由你的 nginx 缓存后,绝大多数访客根本不会产生回源请求:

nginx 缓存(可选,但强烈建议)
# http {} 块内声明一次
proxy_cache_path /var/cache/nginx/sv levels=1 keys_zone=sv_sdk:1m max_size=16m inactive=1h;

# server {} 块内,放在上面的 location /sv/ 之前
location = /sv/v0/api.js {
    proxy_pass https://verify-api.loadren.com/v0/api.js;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Host verify-api.loadren.com;
    proxy_ssl_server_name on;

    proxy_cache sv_sdk;
    proxy_cache_valid 200 5m;
    proxy_cache_use_stale error timeout updating;   # 源站抖动时继续服务旧版本
}
不要把 api.js 下载下来自己托管。 检测规则和脚本会随对抗手法更新,代理 + 缓存能让更新自动流到你的用户; 而写死的副本会逐渐失效——服务端会拒绝过旧版本的 SDK 提交的校验请求。

前端相应使用相对路径(这也是 widget 的默认值):

你的页面
<script src="/sv/v0/api.js" async defer></script>
<div class="sv-widget" data-appid="你的 appid"></div>

服务端校验(siteverify)是你的后端直接发起的,不经过浏览器, 用代理路径或直连地址都可以,按你的网络环境选。

3. 前端 widget

参数

属性必填说明
data-appid你的 appid。
data-endpoint校验服务地址。默认 /sv(第一方代理模式);直连模式填 https://verify-api.loadren.com
data-callback校验成功后调用的全局函数名,参数为令牌。
data-error-callback校验失败时调用的全局函数名,参数为原因。
data-fail-open"true" 时,浏览器侧服务不可达则填入降级标记 sv.failopen。该值永远无法通过服务端校验;你的后端只应在自己调用 siteverify 也失败时才将其视为放行——访客浏览器连不上不代表你的服务器连不上,见到 sv.failopen 就跳过 siteverify 直接放行会成为任何人都能触发的绕过。默认不降级(加载失败提示用户刷新即可)。

校验模式

每个应用可在管理台选择三种模式之一,缺省 invisible

模式用户感知适用场景
invisible(默认)零交互,不注入任何 UI普通页面
interactive按提示点击图片里的几何图形注册、下单等高价值表单
adaptive平时无感,可疑时才要求点击图形兼顾体验与强度

图形由服务端渲染成图片下发(/api/puzzle.png),浏览器只拿到像素, 拿不到任何图形坐标——机器人必须做真正的图像识别才知道点哪儿。同时 SDK 会采集指针轨迹 (仅图片内的相对坐标与相对时间差,不含页面或屏幕绝对坐标、不含页面其他区域的行为), 服务端据此判断操作是否像真人。

图形校验是付费档功能,在用户后台按 key 开启。呈现方式同样在后台配置, 随校验请求下发,页面无法覆盖:

呈现方式效果
嵌入(默认)图片嵌在 widget 元素内
弹窗居中浮层,挂在 <body> 上,不影响页面布局

两种方式都只用 .sv-panel / .sv-prompt / .sv-puzzle 三个类名,样式完全可覆盖。

状态与样式

invisible 模式下 SDK 不注入任何 UIinteractiveadaptive 的升级路径会注入一个 <img class="sv-puzzle"> 和一个 <div class="sv-prompt">,都可以用 CSS 自行定制。 当前状态通过 data-sv-state 属性暴露在 widget 元素上,取值为 pending / solving / puzzle / verifying / escalating / ok / error / failopen

CSS 示例
.sv-widget[data-sv-state="solving"]::after { content: "校验中…"; }
.sv-widget[data-sv-state="ok"]::after      { content: "✓ 已通过"; }
.sv-widget .sv-puzzle { border-radius: 6px; }

JS API

方法说明
simpleVerify.render(el)手动渲染一个 widget(动态插入的表单用)。
simpleVerify.reset(el)作废当前令牌并重新校验(提交失败后重试用)。
simpleVerify.getToken(el)取当前令牌。

元素上还会派发 sv-verifiedsv-error 事件,可用 addEventListener 监听。

reset()同步清空隐藏域中的令牌并把 data-sv-state 置回 pending,然后自动开始新一轮校验。调用后立即读 data-sv-stategetToken() 不会拿到旧令牌,"先查状态、再挂监听"的接入写法是安全的。

4. 服务端校验接口

POST https://verify-api.loadren.com/api/siteverifyapplication/x-www-form-urlencoded

请求字段必填说明
secret你的 appsecret。
response表单里的 sv-response 令牌。
remoteip访客 IP。传了就会强制校验令牌与 IP 的绑定关系。
响应字段说明
success是否通过校验。
score可信度评分。低于服务端阈值的请求在签发令牌前就已被拒绝,所以 success: true 时恒不低于阈值(当前为 60),见下方说明。
challenge_ts令牌签发时间(ISO 8601)。
hostname该 key 登记的域名。
error-codes失败原因数组。
令牌是一次性的。同一个令牌第二次校验会返回 timeout-or-duplicate。 有效期 120 秒,请在收到表单后立即校验。字段与 Turnstile 兼容,从 Turnstile 迁移基本只需换域名和凭据。
score 怎么用。评分在令牌签发时就已把关:低于阈值(当前为 60)的请求拿不到令牌, 所以看 success 即可,通常无需再自设阈值,也不必先"只记录不拦截"观察一轮。 参考分布(当前规则下):正常浏览器 + 有页面交互的真人典型为 85–9060–80 多为环境或交互信号缺失(无时区/语言、页面停留极短等),真人占比仍然很高。 若你的场景要求更严(注册、发帖),建议对 60–80 区间叠加限频、邮箱验证等二次策略,而不是直接拒绝。 阈值与权重由服务端规则热更新,区间可能随对抗情况微调。

5. 错误码

error-code含义与处理
missing-input-secret没传 secret。
invalid-input-secretsecret 不对,或与该令牌所属的 key 不匹配。
missing-input-response没传令牌——通常是前端 widget 还没校验完用户就提交了。
invalid-input-response令牌无效、被篡改,或与 remoteip 不符。
timeout-or-duplicate令牌过期或已被用过。提示用户重试即可。

6. 服务端 SDK

六种语言,接口一致,四个零依赖。完整对照与下载见 SDK 下载

Python
from simple_verify import SimpleVerify, SimpleVerifyError

sv = SimpleVerify("你的 appsecret")
try:
    r = sv.verify(token, remoteip=client_ip)
    if not r.success:
        abort(403)
except SimpleVerifyError:
    pass  # 校验服务不可达 —— 在这里决定放行还是拒绝
Node.js / TypeScript
import { SimpleVerify, SimpleVerifyError } from "@loadren/simple-verify";

const sv = new SimpleVerify(process.env.APP_SECRET);
const r = await sv.verify(token, clientIp);
if (!r.success) return res.status(403).end();
Go
sv := simpleverify.New(os.Getenv("APP_SECRET"))
result, err := sv.Verify(ctx, token, clientIP)
if err != nil { /* 服务不可达:自行决定 fail-open */ }
if !result.Success { http.Error(w, "forbidden", 403); return }
Rust
let sv = SimpleVerify::new(&secret);
match sv.verify(&token, Some(&client_ip)) {
    Ok(r) if r.success => { /* 放行 */ }
    Ok(_) => { /* 拒绝 */ }
    Err(_) => { /* 服务不可达 */ }
}
Java
SimpleVerify sv = new SimpleVerify(System.getenv("APP_SECRET"));
VerifyResult r = sv.verify(token, clientIp);   // 抛 SimpleVerifyException = 服务不可达
if (!r.success) throw new ForbiddenException();
统一约定。所有 SDK 都把"校验被拒"(success == false)和 "服务不可达"(抛异常 / 返回 error)严格区分开——后者由你决定 fail-open 还是 fail-closed, SDK 绝不替你做这个选择。空令牌与超长令牌在本地直接拒绝,不浪费一次网络往返。

7. 调试凭据

联调阶段不想真的解题?用这组内置凭据,它永远放行

appiddeb0deb0deb0deb0deb0deb0deb0deb0
appsecretsv-debug-secret-c0ffee

用它请求校验时会跳过工作量证明与评分直接签发令牌,响应里带 "debug": true 标记。 它自成一个独立应用,令牌与任何真实 key 互不相通,不可能被用来绕过你的线上校验。 上线前记得换回自己的凭据。