Appearance
快速接入
Corptcha 是免安装、无侵入的人机验证服务:在你的网页中放置一个容器、引入 SDK 并初始化,用户完成验证后回调会返回一次性验证令牌(token)。将 token 随表单提交给后端,由后端向验证服务确认后即可放行。
第 1 步:创建站点,获取 Site ID
在控制台的「站点管理」中创建站点,即可获得 Site ID(形如 cpt_xxxxxxxxxxxx)。
Site ID 是公开的,可以安全地出现在前端代码里;请勿泄露密钥 Secret。
第 2 步:引入 SDK
在页面中引入 SDK 文件(仅一个脚本,无任何其它依赖),SDK 托管于国内静态资源 CDN。
html
<!-- 放在 </body> 之前,或使用 defer 属性 -->
<script src="https://res.25y.cn/corptcha/corptcha.iife.js"></script>SDK 文件名固定不变,接入方无需因升级而改动代码。每次发版上传新文件后,请在 CDN 控制台刷新该 URL 的缓存(或将其缓存时间设短,如
no-cache/max-age=60)。自建部署时可改用验证服务自带的/widget/corptcha.iife.js,或通过npm install @corptcha/widget-sdk本地打包引入。
第 3 步:渲染验证码
准备一个容器节点并调用 Corptcha.render() 初始化。
html
<div id="corptcha-widget"></div>
<input type="hidden" id="captcha-token" name="captchaToken" />js
const widget = Corptcha.render(document.querySelector('#corptcha-widget'), {
apiBaseUrl: 'https://cpt-api.25y.cn', // 验证服务 API 地址
siteKey: '你的 Site ID', // 形如 cpt_xxxxxxxxxxxx
purpose: 'login', // 场景标识:login / register / comment 等
language: 'zh-CN',
theme: { mode: 'auto' }, // light / dark / auto,可自定义 accentColor
onSuccess: (token) => {
// 验证通过:把 token 写入表单,随请求提交给后端
document.querySelector('#captcha-token').value = token;
},
onError: (error) => {
console.error(error.errorCode, error.message);
},
onExpired: () => {
// token 过期,需要重新验证
widget.execute();
},
});第 4 步:后端校验 token
前端获取的验证令牌是一次性的,请连同业务请求一起提交给后端。后端调用验证服务核验令牌通过后再执行业务逻辑,不要信任前端传递的任何布尔值。
校验接口(所有语言通用):
- 接口:
POST {apiBaseUrl}/v1/verify - 鉴权:请求头
Authorization: Bearer {Secret}(控制台「站点管理」中的站点密钥) - 请求体:
{ "token": "...", "purpose": "login", "siteKey": "cpt_xxx" } - 成功响应:HTTP 200,
{ "success": true, "siteId": "...", "purpose": "login", "issuedAt": "...", "riskLevel": "low|medium|high|critical", "requestId": "..." } - 失败响应:HTTP 4xx,
{ "errorCode": "TOKEN_NOT_FOUND", "message": "...", "requestId": "..." }
常见错误码:
TOKEN_NOT_FOUND(无效/已使用)、TOKEN_EXPIRED(已过期)、TOKEN_PURPOSE_MISMATCH(用途不匹配)、UNAUTHORIZED(Secret 错误)。
以下各语言示例任选其一,逻辑完全一致:调用核验接口 → 非 200 或 success !== true 即拒绝请求。
Node.js / TypeScript
js
// 建议放在服务端,Node 18+ 原生支持 fetch
const VERIFY_URL = 'https://cpt-api.25y.cn/v1/verify';
async function verifyCaptcha(token, purpose, siteKey, secret) {
const response = await fetch(VERIFY_URL, {
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${secret}`,
},
body: JSON.stringify({ token, purpose, siteKey }),
});
const result = await response.json();
if (!response.ok || result.success !== true) {
throw new Error(`人机验证未通过:${result.errorCode ?? 'UNKNOWN'}`);
}
return result; // result.siteId / result.purpose / result.riskLevel
}Python
python
import requests
VERIFY_URL = "https://cpt-api.25y.cn/v1/verify"
def verify_captcha(token: str, purpose: str, site_key: str, secret: str) -> dict:
response = requests.post(
VERIFY_URL,
headers={"content-type": "application/json",
"authorization": f"Bearer {secret}"},
json={"token": token, "purpose": purpose, "siteKey": site_key},
timeout=5,
)
result = response.json()
if response.status_code != 200 or result.get("success") is not True:
raise ValueError(f"人机验证未通过:{result.get('errorCode', 'UNKNOWN')}")
return result # result["siteId"] / result["purpose"] / result["riskLevel"]Java
java
// 依赖:okhttp 4.x + gson(或任何 JSON 库)
import okhttp3.*;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class CaptchaVerifier {
private static final String VERIFY_URL = "https://cpt-api.25y.cn/v1/verify";
public static JsonObject verifyCaptcha(String token, String purpose,
String siteKey, String secret) throws Exception {
OkHttpClient client = new OkHttpClient();
JsonObject body = new JsonObject();
body.addProperty("token", token);
body.addProperty("purpose", purpose);
body.addProperty("siteKey", siteKey);
Request request = new Request.Builder()
.url(VERIFY_URL)
.post(RequestBody.create(body.toString(), MediaType.get("application/json")))
.header("authorization", "Bearer " + secret)
.build();
try (Response response = client.newCall(request).execute()) {
JsonObject result = JsonParser.parseString(
response.body().string()).getAsJsonObject();
if (!response.isSuccessful() || !result.get("success").getAsBoolean()) {
String code = result.has("errorCode")
? result.get("errorCode").getAsString() : "UNKNOWN";
throw new IllegalStateException("人机验证未通过:" + code);
}
return result; // siteId / purpose / riskLevel
}
}
}Go
go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
const verifyURL = "https://cpt-api.25y.cn/v1/verify"
type verifyResult struct {
Success bool `json:"success"`
SiteID string `json:"siteId"`
Purpose string `json:"purpose"`
RiskLevel string `json:"riskLevel"`
ErrorCode string `json:"errorCode"`
}
func verifyCaptcha(token, purpose, siteKey, secret string) (*verifyResult, error) {
payload, _ := json.Marshal(map[string]string{
"token": token, "purpose": purpose, "siteKey": siteKey,
})
req, err := http.NewRequest(http.MethodPost, verifyURL, bytes.NewReader(payload))
if err != nil {
return nil, err
}
req.Header.Set("content-type", "application/json")
req.Header.Set("authorization", "Bearer "+secret)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result verifyResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK || !result.Success {
return nil, fmt.Errorf("人机验证未通过:%s", result.ErrorCode)
}
return &result, nil
}PHP
php
<?php
// 依赖:cURL 扩展(通常默认开启)
function verifyCaptcha(string $token, string $purpose, string $siteKey, string $secret): array {
$ch = curl_init('https://cpt-api.25y.cn/v1/verify');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'token' => $token,
'purpose' => $purpose,
'siteKey' => $siteKey,
]),
CURLOPT_HTTPHEADER => [
'content-type: application/json',
'authorization: Bearer ' . $secret,
],
CURLOPT_TIMEOUT => 5,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($status !== 200 || ($result['success'] ?? false) !== true) {
throw new RuntimeException('人机验证未通过:' . ($result['errorCode'] ?? 'UNKNOWN'));
}
return $result; // siteId / purpose / riskLevel
}
token只能被成功核验一次:重复使用同一个 token 会返回TOKEN_NOT_FOUND。建议将验证与业务操作放在同一个请求流程中,失败时直接中断业务。

