第一步:拿凭据。在用户后台注册并新建一个 key,得到一对:
appid — 公开值,写在前端页面里appsecret — 私密值,只能放在你的服务端/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 -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":[]}
success 为 true 才继续处理业务。用服务端 SDK 可以省掉手写这段请求。
校验服务托管在 Cloudflare 上。国内访客直连境外节点,api.js 的首次加载
可能要几百毫秒到数秒,网络波动时还会失败。让访客只访问你自己的域名、
由你的服务器转发,可以显著改善这一点:
| 代理模式(推荐) | 直连模式 | |
|---|---|---|
| 国内加载速度 | 走你自己的服务器与 CDN,快 | 取决于访客到 Cloudflare 的链路 |
| 广告拦截插件 | 同源请求,不受影响 | 第三方域名,可能被拦 |
| 跨域 | 无 | 依赖 CORS(已放行) |
| 接入成本 | 一段 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 缓存后,绝大多数访客根本不会产生回源请求:
# 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; # 源站抖动时继续服务旧版本
}
前端相应使用相对路径(这也是 widget 的默认值):
<script src="/sv/v0/api.js" async defer></script>
<div class="sv-widget" data-appid="你的 appid"></div>
服务端校验(siteverify)是你的后端直接发起的,不经过浏览器,
用代理路径或直连地址都可以,按你的网络环境选。
| 属性 | 必填 | 说明 |
|---|---|---|
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 不注入任何 UI;interactive 与
adaptive 的升级路径会注入一个 <img class="sv-puzzle">
和一个 <div class="sv-prompt">,都可以用 CSS 自行定制。
当前状态通过 data-sv-state 属性暴露在 widget 元素上,取值为
pending / solving / puzzle / verifying /
escalating / ok / error / failopen:
.sv-widget[data-sv-state="solving"]::after { content: "校验中…"; }
.sv-widget[data-sv-state="ok"]::after { content: "✓ 已通过"; }
.sv-widget .sv-puzzle { border-radius: 6px; }
| 方法 | 说明 |
|---|---|
simpleVerify.render(el) | 手动渲染一个 widget(动态插入的表单用)。 |
simpleVerify.reset(el) | 作废当前令牌并重新校验(提交失败后重试用)。 |
simpleVerify.getToken(el) | 取当前令牌。 |
元素上还会派发 sv-verified 与 sv-error 事件,可用 addEventListener 监听。
reset() 会同步清空隐藏域中的令牌并把 data-sv-state 置回
pending,然后自动开始新一轮校验。调用后立即读 data-sv-state 或
getToken() 不会拿到旧令牌,"先查状态、再挂监听"的接入写法是安全的。
POST https://verify-api.loadren.com/api/siteverify,application/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 迁移基本只需换域名和凭据。
success 即可,通常无需再自设阈值,也不必先"只记录不拦截"观察一轮。
参考分布(当前规则下):正常浏览器 + 有页面交互的真人典型为 85–90;
60–80 多为环境或交互信号缺失(无时区/语言、页面停留极短等),真人占比仍然很高。
若你的场景要求更严(注册、发帖),建议对 60–80 区间叠加限频、邮箱验证等二次策略,而不是直接拒绝。
阈值与权重由服务端规则热更新,区间可能随对抗情况微调。
| error-code | 含义与处理 |
|---|---|
missing-input-secret | 没传 secret。 |
invalid-input-secret | secret 不对,或与该令牌所属的 key 不匹配。 |
missing-input-response | 没传令牌——通常是前端 widget 还没校验完用户就提交了。 |
invalid-input-response | 令牌无效、被篡改,或与 remoteip 不符。 |
timeout-or-duplicate | 令牌过期或已被用过。提示用户重试即可。 |
六种语言,接口一致,四个零依赖。完整对照与下载见 SDK 下载。
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 # 校验服务不可达 —— 在这里决定放行还是拒绝
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();
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 }
let sv = SimpleVerify::new(&secret);
match sv.verify(&token, Some(&client_ip)) {
Ok(r) if r.success => { /* 放行 */ }
Ok(_) => { /* 拒绝 */ }
Err(_) => { /* 服务不可达 */ }
}
SimpleVerify sv = new SimpleVerify(System.getenv("APP_SECRET"));
VerifyResult r = sv.verify(token, clientIp); // 抛 SimpleVerifyException = 服务不可达
if (!r.success) throw new ForbiddenException();
success == false)和
"服务不可达"(抛异常 / 返回 error)严格区分开——后者由你决定 fail-open 还是 fail-closed,
SDK 绝不替你做这个选择。空令牌与超长令牌在本地直接拒绝,不浪费一次网络往返。
联调阶段不想真的解题?用这组内置凭据,它永远放行:
| appid | deb0deb0deb0deb0deb0deb0deb0deb0 |
| appsecret | sv-debug-secret-c0ffee |
用它请求校验时会跳过工作量证明与评分直接签发令牌,响应里带 "debug": true 标记。
它自成一个独立应用,令牌与任何真实 key 互不相通,不可能被用来绕过你的线上校验。
上线前记得换回自己的凭据。