# Concepts (/docs/concepts) Data model [#data-model] Site [#site] Represents a domain or application where the CAPTCHA is deployed. Each site has a **Site Key** (`pk_...`) for the client and a **Secret Key** (`sk_...`) for server-side verification. One user can own many sites. Image Set [#image-set] A named pool of images (e.g., "NYC Parks", "Fire Hydrants"). Owned by a user — not a specific site — so it can be reused across multiple sites. Image [#image] A single image within an image set. Has an `id`, a public `url` (hosted on Cloudflare R2), and an optional `name`. Images have no inherent labels — the puzzle defines which images are correct. Puzzle [#puzzle] Belongs to a site and references an image set. Contains: * **Prompt** — the keyword shown to the user (e.g., "parks"). The widget displays it with a fixed instruction prefix. * **Correct images** — which images from the set are the right answers * **Correct count** — how many correct images to show per challenge (randomly selected from the correct pool each time) * **Incorrect images** — optional hand-picked distractors. If not set, distractors are drawn randomly from the remaining images in the set * **Difficulty** — a float from 0 to 1 (default 0.5) that controls the pass threshold * **Enabled** — toggle to disable a puzzle without deleting it CAPTCHA Session [#captcha-session] Stored in Redis with a 5-minute TTL. Challenge sessions hold all verification data (image order, correct answers, difficulty). Verified sessions are created after passing and consumed atomically by siteverify. *** Verification flow [#verification-flow] ``` Browser yCAPTCHA Server Your Backend | | | | 1. Load /widget/{siteKey} | | |--------------------------------->| | | | | | 2. POST /challenge {siteKey} | | |--------------------------------->| | | { sessionToken, prompt, images} | | |<---------------------------------| | | | | | 3. User selects images | | | | | | 4. POST /verify | | | {sessionToken, selectedIndices} | |--------------------------------->| | | { success: true, token } | | |<---------------------------------| | | | | | 5. postMessage to parent page | | | | | | 6. Form submit with token | | |-------------------------------------------------->| | | | | | | 7. POST /siteverify | | | {token, secretKey} | | |<------------------------| | | { success: true } | | |------------------------>| | | | | | 8. Accept/reject ``` *** Scoring logic [#scoring-logic] Grid assembly [#grid-assembly] Every challenge serves exactly **9 images** in a 3x3 grid. The server randomly picks `correctCount` images from the puzzle's correct pool, fills remaining slots with distractors (hand-picked or random), then shuffles with Fisher-Yates. The widget receives only proxy URLs — it cannot see image IDs or distinguish correct from incorrect. Pass threshold [#pass-threshold] The number of correct selections required to pass is: ``` requiredCorrect = Math.ceil(correctCount * difficulty) ``` | correctCount | Difficulty | Required selections | | ------------ | ---------- | ------------------- | | 3 | 0.5 | 2 | | 3 | 1.0 | 3 | | 3 | 0.25 | 1 | | 5 | 0.5 | 3 | Anti-bot rule [#anti-bot-rule] If all 9 grid positions are selected, the attempt **automatically fails** before any threshold check. This catches bots that select everything to guarantee hitting all correct images. Wrong selection penalty [#wrong-selection-penalty] Each incorrect selection subtracts 1 from the score. The final score is `correctSelections - wrongSelections`, and this must meet or exceed the required threshold. This prevents attackers from selecting large subsets to guarantee enough correct hits. *** Token lifecycle [#token-lifecycle] 1. **Challenge session** created in Redis when `/challenge` is called (5-minute TTL) 2. `/verify` atomically consumes the challenge session (prevents replay) and creates a **verified session** in Redis 3. `/siteverify` atomically consumes the verified session (one-time use) and validates the secret key 4. Any subsequent call with the same token returns `"Invalid token"` # Quick Start (/docs) Introduction [#introduction] yCAPTCHA (your-CAPTCHA) is a CAPTCHA widget built for small communities and subcultures. You upload your own images and build challenges around shared references — subway lines, rhythm game charts, niche memes — instead of generic traffic lights. It is **not a serious bot-detection service**: there is no ML, no fingerprinting, no behavioral analysis. If you need real bot protection, use [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/) or hCaptcha. yCAPTCHA is about the vibe. Getting your API Key [#getting-your-api-key] Create a site and get API key in [Dashboard](https://ycaptcha.xyspg.moe/dashboard). Never expose your Secret Key (`sk_...`) in client-side code. Embed in 2 lines [#embed-in-2-lines] ```html
``` Work with LLMs [#work-with-llms] ``` https://ycaptcha.xyspg.moe/llms-full.txt ``` Next steps [#next-steps] Full endpoint documentation. # Create a CAPTCHA challenge (/docs/api-reference/challenge) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new challenge session with a randomized image grid. Called automatically by the widget — you do not need to call this directly. # Fetch a challenge image (/docs/api-reference/image) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Proxies a challenge image from storage without exposing the real URL. Called automatically by the widget — you do not need to call this directly. # Overview (/docs/api-reference) Base URL: ``` https://ycaptcha.xyspg.moe ``` Public Endpoint [#public-endpoint] The only endpoint your backend needs to call: Verify a CAPTCHA token server-side. Requires your Secret Key. Widget Internal Endpoints [#widget-internal-endpoints] These are called automatically by the widget. You do not need to interact with them directly. Create a new challenge session with a randomized image grid. Submit the user's image selections for verification. Proxy a challenge image without exposing the real storage URL. Rate Limiting [#rate-limiting] Per-IP sliding window rate limits are enforced on all API routes: | Route | Limit | | ------------- | ---------------- | | `/siteverify` | 100 requests/min | | `/challenge` | 20 requests/min | | `/verify` | 10 requests/min | | `/image` | 60 requests/min | # Verify a CAPTCHA token (/docs/api-reference/siteverify) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Verifies a one-time token after the user completed the CAPTCHA challenge. This is the only endpoint your backend needs to call. **Must be called from your server — never from the browser.** # Submit challenge answer (/docs/api-reference/verify) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Submits the user's image selections for verification. Called automatically by the widget — you do not need to call this directly. # Create a Site (/docs/get-started/create-site) Before embedding the widget, you need to create a site in the yCAPTCHA dashboard to get your Site Key and Secret Key. 1\. Create a site [#1-create-a-site] 1. Log into the [yCAPTCHA dashboard](https://ycaptcha.xyspg.moe/dashboard). 2. Navigate to **Sites** and click **Create Site**. 3. Give your site a name (e.g., "My Blog", "Login Page"). 4. Your site will be issued two keys: | Key | Format | Purpose | | ---------- | -------- | --------------------------------------------------- | | Site Key | `pk_...` | Public. Used in the widget iframe URL. | | Secret Key | `sk_...` | Private. Used only on your server for verification. | > **Never expose your Secret Key in client-side code.** It should only be used in server-to-server API calls. 2\. Upload images [#2-upload-images] 1. Navigate to **Image Sets** in the dashboard. 2. Create a new image set (e.g., "NYC Parks", "Fire Hydrants"). 3. Upload the images you want to use in your challenges. Image sets are owned by your account, not a specific site. You can reuse the same image set across multiple sites. 3\. Create a puzzle [#3-create-a-puzzle] 1. Navigate to your site's **Puzzles** section. 2. Click **Create Puzzle** and configure: | Setting | Description | | ---------------- | ------------------------------------------------------------------------------------------- | | Image Set | Select which image pool to use for this puzzle. | | Prompt | The keyword shown to users (e.g., `parks`). The widget adds a fixed instruction prefix. | | Correct Images | Mark which images in the set are the right answers. | | Incorrect Images | Optionally hand-pick distractors. If left empty, they are drawn randomly from the pool. | | Difficulty | Float from 0 to 1 (default 0.5). Controls how many correct selections are required to pass. | Prompt [#prompt] Enter just the keyword (e.g., `parks`, `fire hydrants`). The widget always displays "Select all images with" as the instruction prefix. Difficulty [#difficulty] The pass threshold is calculated as: ``` requiredCorrect = Math.ceil(correctImageCount * difficulty) ``` For example, with 3 correct images and difficulty 0.5, the user needs to select at least 2 correct images. Next steps [#next-steps] Once you have a site with at least one puzzle configured: 1. [Embed the Widget](/docs/get-started/embed-widget) on your webpage 2. [Validate tokens](/docs/get-started/server-validation) on your server # Embed the Widget (/docs/get-started/embed-widget) Add yCAPTCHA to your site with two lines of HTML. The script handles everything — creating the widget, managing the challenge flow, resizing, and injecting a hidden form input with the verification token. Prerequisites [#prerequisites] * A yCAPTCHA site with at least one [configured puzzle](/docs/get-started/create-site) * Your Site Key (`pk_...`) *** Quick start [#quick-start] ```html
``` That's it. The script automatically finds all `.y-captcha` elements on the page, creates the widget, and inserts a hidden `` next to it. When the user passes the challenge, the input is filled with the verification token — ready to submit with your form. *** Form integration [#form-integration] ```html
``` On form submission, the token is sent as `ycaptcha-response` in the form body. [Validate it on your server](/docs/get-started/server-validation). *** Explicit rendering [#explicit-rendering] If you need more control, use the JavaScript API instead of auto-rendering: ```html
``` *** JavaScript API [#javascript-api] The script exposes `window.ycaptcha` with these methods: `ycaptcha.render(container, params)` [#ycaptcharendercontainer-params] Renders a widget into a container element. | Parameter | Type | Description | | ------------------------------- | ----------------------- | ------------------------------------------------------- | | `container` | `string \| HTMLElement` | CSS selector or DOM element | | `params.sitekey` | `string` | Your site key (overrides `data-sitekey`) | | `params.callback` | `function` | Called with token on success | | `params["expired-callback"]` | `function` | Called when token expires | | `params["error-callback"]` | `function` | Called with error message on failure | | `params["response-field-name"]` | `string` | Custom hidden input name (default: `ycaptcha-response`) | Returns a widget ID string. `ycaptcha.getResponse(widgetId?)` [#ycaptchagetresponsewidgetid] Returns the current verification token, or `null` if not yet verified. `ycaptcha.reset(widgetId?)` [#ycaptcharesetwidgetid] Resets the widget to its initial state. Use this after form submission to allow the user to solve again. `ycaptcha.remove(widgetId?)` [#ycaptcharemovewidgetid] Removes the widget from the DOM and cleans up. `ycaptcha.isExpired(widgetId?)` [#ycaptchaisexpiredwidgetid] Returns `true` if the token has expired. > If `widgetId` is omitted, all methods operate on the most recently created widget. *** Data attributes [#data-attributes] As an alternative to the JavaScript API, you can configure widgets with `data-*` attributes: ```html
``` The attribute values are global function names that will be called when the corresponding event fires. *** Events [#events] The widget communicates via `postMessage` internally. If you need low-level access (e.g., for a framework integration), these are the events: | Event | Payload | Description | | --------- | ------------------- | ---------------------------------------------- | | `success` | `{ token: string }` | User passed the CAPTCHA | | `expired` | — | 5-minute session elapsed | | `error` | `{ code, message }` | Fatal error (invalid siteKey, network failure) | | `resize` | `{ width, height }` | Widget dimensions changed | All messages include `source: "ycaptcha"` for filtering. *** Next steps [#next-steps] After embedding the widget, [validate the token on your server](/docs/get-started/server-validation). # Overview (/docs/get-started) This guide will get you started on embedding the yCAPTCHA widget and verifying its tokens server-side. yCAPTCHA is a community-puzzle widget, not a bot-detection service — see the [FAQ](/#faq) if you haven't already. Prerequisites [#prerequisites] Before you begin, you must have: * A yCAPTCHA account * A website or web application where you want to add the widget * Basic knowledge of HTML and your preferred server-side language *** Process [#process] yCAPTCHA widgets are the user-facing challenge that runs on your page. Each site gets its own unique key pair and puzzle configuration. | Component | Description | | ---------- | ----------------------------------------------------------------- | | Site Key | Public key (`pk_...`) used to invoke the widget on your site. | | Secret Key | Private key (`sk_...`) used for server-side token validation. | | Puzzle | Image challenge with configurable correct answers and difficulty. | > Regardless of how you create your site and puzzles, you will still need to [embed the widget](/docs/get-started/embed-widget) on your webpage and [validate the token](/docs/get-started/server-validation) on your server. Implementing yCAPTCHA involves two essential components: 1. **Client-side**: [Embed the widget](/docs/get-started/embed-widget) — Add the yCAPTCHA iframe to your webpage to challenge visitors and generate verification tokens. 2. **Server-side**: [Validate the token](/docs/get-started/server-validation) — Verify the tokens on your server using the Siteverify API to ensure they are authentic. *** Implementation [#implementation] Follow the steps below to implement yCAPTCHA. 1\. Create your site and puzzle [#1-create-your-site-and-puzzle] First, create a site and configure at least one puzzle to get your keys. Refer to [Create a Site](/docs/get-started/create-site) for step-by-step instructions on creating sites, uploading images, and configuring puzzles. 2\. Embed the widget [#2-embed-the-widget] Add the yCAPTCHA widget iframe to your webpage and listen for verification tokens via `postMessage`. Refer to [Embed the Widget](/docs/get-started/embed-widget) to learn about embedding, resize handling, and event listening. 3\. Validate tokens [#3-validate-tokens] Implement server-side validation to verify the tokens generated by the widget. Refer to [Server-side Validation](/docs/get-started/server-validation) to secure your implementation with proper token verification. *** Security requirements [#security-requirements] * **Server-side validation is mandatory.** You must call the Siteverify API to verify every token. Not verifying leaves your implementation vulnerable. * **Tokens expire after 5 minutes.** Each token can only be validated once. Expired or used tokens must be replaced with fresh challenges. * **Protect your Secret Key.** Never expose your `sk_...` key in client-side code. # Server-side Validation (/docs/get-started/server-validation) After the user completes the CAPTCHA, you receive a one-time token via `postMessage`. You **must** verify this token on your server before accepting the form submission. This call must always happen on your server — never in the browser. Endpoint [#endpoint] ``` POST https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify ``` Request [#request] | Field | Type | Required | Description | | ----------- | -------- | -------- | ------------------------------------------ | | `token` | `string` | Yes | The token from the widget's `postMessage`. | | `secretKey` | `string` | Yes | Your site's secret key (`sk_...`). | ```json { "token": "aB3dEfGh...64chars", "secretKey": "sk_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456" } ``` Response [#response] Success [#success] ```json { "success": true } ``` Failure [#failure] All failures return HTTP 200 with `success: false` and an `error` field: | Error | Meaning | | ------------------------------ | ------------------------------------------------------------- | | `"Missing token or secretKey"` | Request body missing required fields. | | `"Invalid token"` | Token not found — never issued, already consumed, or expired. | | `"Invalid secretKey"` | Secret key doesn't match the site owning this puzzle. | Examples [#examples] Node.js (fetch) [#nodejs-fetch] ```js const response = await fetch( "https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token: req.body.captchaToken, secretKey: process.env.YCAPTCHA_SECRET_KEY, }), }, ); const result = await response.json(); if (!result.success) { return res.status(400).json({ error: "CAPTCHA verification failed" }); } // Proceed with form handling ``` Python (requests) [#python-requests] ```python import requests result = requests.post( "https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify", json={ "token": request.form["ycaptcha-response"], "secretKey": os.environ["YCAPTCHA_SECRET_KEY"], }, ).json() if not result.get("success"): abort(400, "CAPTCHA verification failed") ``` Important notes [#important-notes] * **Tokens are single-use.** On a successful verification, the session is permanently deleted. Calling siteverify again with the same token returns `"Invalid token"`. * **Always check `success === true` explicitly.** A 200 HTTP status alone does not mean success. * The `secretKey` is verified by joining through the puzzle to its site — a token from one site cannot be validated with another site's key. # 核心概念 (/docs/concepts) 数据模型 [#数据模型] 站点(Site) [#站点site] 代表部署验证码的域名或应用程序。每个站点有一个用于客户端的**站点密钥**(`pk_...`)和一个用于服务器端验证的**私有密钥**(`sk_...`)。一个用户可以拥有多个站点。 图片集(Image Set) [#图片集image-set] 命名的图片池(例如"纽约公园"、"消防栓")。归用户所有——而非特定站点——因此可以在多个站点之间重复使用。 图片(Image) [#图片image] 图片集中的单张图片。具有 `id`、公共 `url`(托管在 Cloudflare R2 上)和可选的 `name`。图片本身没有标签——题目定义了哪些图片是正确的。 题目(Puzzle) [#题目puzzle] 属于某个站点并引用一个图片集。包含: * **提示词** — 显示给用户的关键词(例如"公园")。组件使用固定的指令前缀显示它。 * **正确图片** — 图片集中哪些图片是正确答案 * **正确数量** — 每次挑战显示多少张正确图片(每次从正确池中随机选择) * **错误图片** — 可选的手动选择干扰项。如果未设置,干扰项将从图片集中的剩余图片中随机抽取 * **难度** — 0 到 1 之间的浮点数(默认 0.5),控制通过阈值 * **启用状态** — 可以禁用题目而不删除它 验证码会话(CAPTCHA Session) [#验证码会话captcha-session] 存储在 Redis 中,TTL 为 5 分钟。挑战会话保存所有验证数据(图片顺序、正确答案、难度)。验证会话在通过后创建,并由 siteverify 原子性地消费。 *** 验证流程 [#验证流程] ``` 浏览器 yCAPTCHA 服务器 你的后端 | | | | 1. 加载 /widget/{siteKey} | | |--------------------------------->| | | | | | 2. POST /challenge {siteKey} | | |--------------------------------->| | | { sessionToken, prompt, images} | | |<---------------------------------| | | | | | 3. 用户选择图片 | | | | | | 4. POST /verify | | | {sessionToken, selectedIndices} | |--------------------------------->| | | { success: true, token } | | |<---------------------------------| | | | | | 5. 通过 postMessage 发送给父页面 | | | | | | 6. 表单提交(附带令牌) | | |-------------------------------------------------->| | | | | | | 7. POST /siteverify | | | {token, secretKey} | | |<------------------------| | | { success: true } | | |------------------------>| | | | | | 8. 接受/拒绝请求 ``` *** 评分逻辑 [#评分逻辑] 网格组装 [#网格组装] 每次挑战固定提供 **9 张图片**,排列为 3x3 网格。服务器从题目的正确池中随机选择 `correctCount` 张图片,用干扰项(手动选择或随机)填充剩余位置,然后使用 Fisher-Yates 算法洗牌。组件只接收代理 URL——无法看到图片 ID 或区分正确与错误。 通过阈值 [#通过阈值] 通过所需的正确选择数量为: ``` requiredCorrect = Math.ceil(correctCount * difficulty) ``` | correctCount | 难度 | 所需正确选择数 | | ------------ | ---- | ------- | | 3 | 0.5 | 2 | | 3 | 1.0 | 3 | | 3 | 0.25 | 1 | | 5 | 0.5 | 3 | 反机器人规则 [#反机器人规则] 如果所有 9 个网格位置都被选中,该次尝试**自动失败**,不进行任何阈值检查。这可以捕获那些选择所有图片以确保命中所有正确图片的机器人。 错误选择惩罚 [#错误选择惩罚] 目前,选择错误图片**不会**降低分数。只评估选中的正确图片数量。 *** 令牌生命周期 [#令牌生命周期] 1. 调用 `/challenge` 时在 Redis 中创建**挑战会话**(5 分钟 TTL) 2. `/verify` 原子性地消费挑战会话(防止重放)并在 Redis 中创建**验证会话** 3. `/siteverify` 原子性地消费验证会话(一次性使用)并验证私有密钥 4. 使用相同令牌的任何后续调用都会返回 `"Invalid token"` # 概述 (/docs) 什么是 yCAPTCHA? [#什么是-ycaptcha] yCAPTCHA 是为小圈子和亚文化社群做的验证码组件。你上传自己的图片,围绕社群共同的梗做挑战——地铁线路、音游谱面、小众梗图——代替千篇一律的红绿灯。 它**不是认真的 bot 检测服务**:没有机器学习、没有指纹识别、没有行为分析。要真防 bot 请用 [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/) 或 hCaptcha。yCAPTCHA 关心的是氛围。 工作原理 [#工作原理] 实现 yCAPTCHA 涉及两个协同工作的基本组件: 1. **客户端**:[嵌入组件](/docs/get-started/embed-widget) — 将 yCAPTCHA iframe 添加到你的网页中。用户解决图片选择题目后会生成验证令牌。 2. **服务器端**:[验证令牌](/docs/get-started/server-validation) — 使用 Siteverify API 在你的服务器上验证令牌,确保其真实且未被篡改。 | 组件 | 描述 | | ---- | -------------------------- | | 站点密钥 | 公钥(`pk_...`),用于在你的页面上加载组件。 | | 私有密钥 | 私钥(`sk_...`),仅用于服务器端令牌验证。 | | 题目 | 你在控制面板中配置的图片挑战。 | 安全要求 [#安全要求] * **服务器端验证是必须的。** 永远不要仅信任客户端。你必须调用 [Siteverify API](/docs/api-reference/siteverify) 来确认每个令牌。 * **令牌是一次性的。** 一旦验证,令牌将被永久删除且不能重复使用。 * **令牌在 5 分钟后过期。** 过期的令牌会被 Siteverify API 拒绝。 * **保护你的私有密钥。** 永远不要在客户端代码中暴露 `sk_...`。 下一步 [#下一步] * [核心概念](/docs/concepts) — 了解数据模型、验证流程和评分逻辑 * [快速开始](/docs/get-started) — 集成 yCAPTCHA 的分步指南 * [API 参考](/docs/api-reference) — 完整的接口文档 # Create a CAPTCHA challenge (/docs/api-reference/challenge) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new challenge session with a randomized image grid. Called automatically by the widget — you do not need to call this directly. # Fetch a challenge image (/docs/api-reference/image) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Proxies a challenge image from storage without exposing the real URL. Called automatically by the widget — you do not need to call this directly. # API 参考 (/docs/api-reference) 基础 URL: ``` https://ycaptcha.xyspg.moe ``` 接口 [#接口] | 接口 | 用途 | 调用方 | | ------------------------------------------------------------------- | ----------- | -------- | | [`POST /api/v0/captcha/siteverify`](/docs/api-reference/siteverify) | 服务器端验证验证码令牌 | **你的后端** | `/challenge`、`/verify` 和 `/image` 接口是组件内部使用的,会自动调用。你不需要直接与它们交互。 认证 [#认证] **siteverify** 接口需要你的私有密钥(`sk_...`),且只能从你的服务器调用。永远不要在客户端代码中暴露你的私有密钥。 速率限制 [#速率限制] 所有 API 路由都强制执行基于 IP 的滑动窗口速率限制: | 路由 | 限制 | | ------------- | ------------ | | `/siteverify` | 100 次请求/分钟 | | 组件内部路由 | 10-60 次请求/分钟 | # POST /siteverify (/docs/api-reference/siteverify) ``` POST /api/v0/captcha/siteverify ``` 在用户完成验证码后验证令牌。**这是你的后端唯一需要调用的接口。** > **需要你的私有密钥——永远不要从浏览器调用此接口。** 请求体 [#请求体] | 字段 | 类型 | 必填 | 描述 | | ----------- | -------- | -- | ----------------------- | | `token` | `string` | 是 | 来自组件 `postMessage` 的令牌。 | | `secretKey` | `string` | 是 | 你站点的私有密钥(`sk_...`)。 | ```json { "token": "aB3dEfGh...64chars", "secretKey": "sk_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456" } ``` 响应 — 成功 (200 OK) [#响应--成功-200-ok] ```json { "success": true } ``` 响应 — 失败 (200 OK) [#响应--失败-200-ok] 所有失败都返回 HTTP 200,`success: false` 以及一个 `error` 字段: | 错误 | 含义 | | ------------------------------ | --------------------- | | `"Missing token or secretKey"` | 请求体缺少必填字段。 | | `"Invalid token"` | 未找到令牌——从未签发、已被消费或已过期。 | | `"Invalid secretKey"` | 私有密钥与拥有此题目的站点不匹配。 | 错误 (400 Bad Request) [#错误-400-bad-request] ```json { "success": false, "error": "Missing token or secretKey" } ``` 重要说明 [#重要说明] * **令牌是一次性的。** 成功后,会话从 Redis 中被原子性地消费。再次调用 siteverify 将返回 `"Invalid token"`。 * `secretKey` 通过题目关联到其站点进行验证——一个站点的令牌不能用另一个站点的密钥验证。 * 始终显式检查 `result.success === true`。仅有 200 状态码并不意味着成功。 # Submit challenge answer (/docs/api-reference/verify) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Submits the user's image selections for verification. Called automatically by the widget — you do not need to call this directly. # 创建站点 (/docs/get-started/create-site) 在嵌入组件之前,你需要在 yCAPTCHA 控制面板中创建一个站点以获取站点密钥和私有密钥。 1\. 创建站点 [#1-创建站点] 1. 登录 [yCAPTCHA 控制面板](https://ycaptcha.xyspg.moe/dashboard)。 2. 导航到**站点**并点击**创建站点**。 3. 为你的站点命名(例如"我的博客"、"登录页面")。 4. 你的站点将获得两个密钥: | 密钥 | 格式 | 用途 | | ---- | -------- | ------------------- | | 站点密钥 | `pk_...` | 公开。用于组件 iframe URL。 | | 私有密钥 | `sk_...` | 私密。仅用于服务器端验证。 | > **永远不要在客户端代码中暴露你的私有密钥。** 它应该只在服务器到服务器的 API 调用中使用。 2\. 上传图片 [#2-上传图片] 1. 在控制面板中导航到**图片集**。 2. 创建一个新的图片集(例如"纽约公园"、"消防栓")。 3. 上传你想在挑战中使用的图片。 图片集归你的账户所有,而非特定站点。你可以在多个站点之间重复使用同一个图片集。 3\. 创建题目 [#3-创建题目] 1. 导航到你站点的**题目**部分。 2. 点击**创建题目**并配置: | 设置 | 描述 | | ---- | ----------------------------------- | | 图片集 | 选择用于此题目的图片池。 | | 提示词 | 显示给用户的关键词(例如 `parks`)。组件会添加固定的指令前缀。 | | 正确图片 | 标记图片集中哪些图片是正确答案。 | | 错误图片 | 可选地手动选择干扰项。如果留空,将从图片池中随机抽取。 | | 难度 | 0 到 1 之间的浮点数(默认 0.5)。控制通过所需的正确选择数量。 | 提示词 [#提示词] 只需输入关键词(例如 `parks`、`fire hydrants`)。组件始终显示固定的指令前缀。 难度 [#难度] 通过阈值的计算方式为: ``` requiredCorrect = Math.ceil(correctImageCount * difficulty) ``` 例如,3 张正确图片和难度 0.5 时,用户需要至少选择 2 张正确图片。 下一步 [#下一步] 一旦你有了配置了至少一个题目的站点: 1. 在你的网页上[嵌入组件](/docs/get-started/embed-widget) 2. 在你的服务器上[验证令牌](/docs/get-started/server-validation) # 嵌入组件 (/docs/get-started/embed-widget) 只需两行 HTML 即可将 yCAPTCHA 添加到你的站点。脚本会处理一切——创建组件、管理挑战流程、调整大小,以及注入带有验证令牌的隐藏表单输入。 前提条件 [#前提条件] * 一个至少配置了一个[题目](/docs/get-started/create-site)的 yCAPTCHA 站点 * 你的站点密钥(`pk_...`) *** 快速开始 [#快速开始] ```html
``` 就这样。脚本会自动查找页面上所有的 `.y-captcha` 元素,创建组件,并在旁边插入一个隐藏的 ``。当用户通过挑战时,输入框会填入验证令牌——可以随时与你的表单一起提交。 *** 表单集成 [#表单集成] ```html
``` 提交表单时,令牌以 `ycaptcha-response` 的形式包含在表单主体中。在你的服务器上[验证它](/docs/get-started/server-validation)。 *** 手动渲染 [#手动渲染] 如果你需要更多控制,可以使用 JavaScript API 代替自动渲染: ```html
``` *** JavaScript API [#javascript-api] 脚本暴露了 `window.ycaptcha`,包含以下方法: `ycaptcha.render(container, params)` [#ycaptcharendercontainer-params] 将组件渲染到容器元素中。 | 参数 | 类型 | 描述 | | ------------------------------- | ----------------------- | --------------------------------- | | `container` | `string \| HTMLElement` | CSS 选择器或 DOM 元素 | | `params.sitekey` | `string` | 你的站点密钥(覆盖 `data-sitekey`) | | `params.callback` | `function` | 成功时携带令牌调用 | | `params["expired-callback"]` | `function` | 令牌过期时调用 | | `params["error-callback"]` | `function` | 失败时携带错误信息调用 | | `params["response-field-name"]` | `string` | 自定义隐藏输入名称(默认:`ycaptcha-response`) | 返回组件 ID 字符串。 `ycaptcha.getResponse(widgetId?)` [#ycaptchagetresponsewidgetid] 返回当前验证令牌,如果尚未验证则返回 `null`。 `ycaptcha.reset(widgetId?)` [#ycaptcharesetwidgetid] 将组件重置为初始状态。在表单提交后使用此方法,允许用户重新解题。 `ycaptcha.remove(widgetId?)` [#ycaptcharemovewidgetid] 从 DOM 中移除组件并清理资源。 `ycaptcha.isExpired(widgetId?)` [#ycaptchaisexpiredwidgetid] 如果令牌已过期则返回 `true`。 > 如果省略 `widgetId`,所有方法都作用于最近创建的组件。 *** Data 属性 [#data-属性] 作为 JavaScript API 的替代方案,你可以使用 `data-*` 属性配置组件: ```html
``` 属性值是全局函数名称,当相应事件触发时会被调用。 *** 事件 [#事件] 组件在内部通过 `postMessage` 通信。如果你需要底层访问(例如框架集成),以下是事件列表: | 事件 | 负载 | 描述 | | --------- | ------------------- | --------------------- | | `success` | `{ token: string }` | 用户通过了验证码 | | `expired` | — | 5 分钟会话已过期 | | `error` | `{ code, message }` | 致命错误(无效 siteKey、网络故障) | | `resize` | `{ width, height }` | 组件尺寸发生变化 | 所有消息包含 `source: "ycaptcha"` 用于过滤。 *** 下一步 [#下一步] 嵌入组件后,在你的服务器上[验证令牌](/docs/get-started/server-validation)。 # 概述 (/docs/get-started) 本指南将帮助你在页面上嵌入 yCAPTCHA 组件,并在服务端验证它发出的 token。yCAPTCHA 是一个社群梗图挑战组件,不是 bot 检测服务——详见[常见问题](/#faq)。 前提条件 [#前提条件] 开始之前,你需要: * 一个 yCAPTCHA 账户 * 想要添加组件的网站或 Web 应用 * 基本的 HTML 和服务器端编程知识 *** 流程 [#流程] yCAPTCHA 组件是页面上面向用户的挑战界面。每个站点获得自己独特的密钥对和题目配置。 | 组件 | 描述 | | ---- | -------------------------- | | 站点密钥 | 公钥(`pk_...`),用于在你的站点上调用组件。 | | 私有密钥 | 私钥(`sk_...`),用于服务器端令牌验证。 | | 题目 | 具有可配置正确答案和难度的图片挑战。 | > 无论你如何创建站点和题目,你仍然需要在网页上[嵌入组件](/docs/get-started/embed-widget)并在服务器上[验证令牌](/docs/get-started/server-validation)。 实现 yCAPTCHA 涉及两个基本组件: 1. **客户端**:[嵌入组件](/docs/get-started/embed-widget) — 将 yCAPTCHA iframe 添加到你的网页中,向访客发起挑战并生成验证令牌。 2. **服务器端**:[验证令牌](/docs/get-started/server-validation) — 使用 Siteverify API 在你的服务器上验证令牌,确保其真实性。 *** 实现步骤 [#实现步骤] 按照以下步骤实现 yCAPTCHA。 1\. 创建站点和题目 [#1-创建站点和题目] 首先,创建一个站点并配置至少一个题目以获取你的密钥。 参考[创建站点](/docs/get-started/create-site)获取创建站点、上传图片和配置题目的分步说明。 2\. 嵌入组件 [#2-嵌入组件] 将 yCAPTCHA 组件 iframe 添加到你的网页,并通过 `postMessage` 监听验证令牌。 参考[嵌入组件](/docs/get-started/embed-widget)了解嵌入、调整大小处理和事件监听。 3\. 验证令牌 [#3-验证令牌] 实现服务器端验证以验证组件生成的令牌。 参考[服务器端验证](/docs/get-started/server-validation)以通过正确的令牌验证保护你的实现。 *** 安全要求 [#安全要求] * **服务器端验证是必须的。** 你必须调用 Siteverify API 来验证每个令牌。不验证会使你的实现容易受到攻击。 * **令牌在 5 分钟后过期。** 每个令牌只能验证一次。过期或已使用的令牌必须用新的挑战替换。 * **保护你的私有密钥。** 永远不要在客户端代码中暴露你的 `sk_...` 密钥。 # 服务器端验证 (/docs/get-started/server-validation) 用户完成验证码后,你会通过 `postMessage` 收到一个一次性令牌。你**必须**在接受表单提交之前在服务器上验证此令牌。 此调用必须始终在你的服务器上进行——永远不要在浏览器中进行。 接口地址 [#接口地址] ``` POST https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify ``` 请求 [#请求] | 字段 | 类型 | 必填 | 描述 | | ----------- | -------- | -- | ----------------------- | | `token` | `string` | 是 | 来自组件 `postMessage` 的令牌。 | | `secretKey` | `string` | 是 | 你站点的私有密钥(`sk_...`)。 | ```json { "token": "aB3dEfGh...64chars", "secretKey": "sk_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456" } ``` 响应 [#响应] 成功 [#成功] ```json { "success": true } ``` 失败 [#失败] 所有失败都返回 HTTP 200,`success: false` 以及一个 `error` 字段: | 错误 | 含义 | | ------------------------------ | --------------------- | | `"Missing token or secretKey"` | 请求体缺少必填字段。 | | `"Invalid token"` | 未找到令牌——从未签发、已被消费或已过期。 | | `"Invalid secretKey"` | 私有密钥与拥有此题目的站点不匹配。 | 示例 [#示例] Node.js (fetch) [#nodejs-fetch] ```js const response = await fetch( "https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token: req.body.captchaToken, secretKey: process.env.YCAPTCHA_SECRET_KEY, }), }, ); const result = await response.json(); if (!result.success) { return res.status(400).json({ error: "验证码验证失败" }); } // 继续处理表单 ``` Python (requests) [#python-requests] ```python import requests result = requests.post( "https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify", json={ "token": request.form["captcha-token"], "secretKey": os.environ["YCAPTCHA_SECRET_KEY"], }, ).json() if not result.get("success"): abort(400, "验证码验证失败") ``` PHP (cURL) [#php-curl] ```php $response = json_decode(file_get_contents('https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify', false, stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => 'Content-Type: application/json', 'content' => json_encode([ 'token' => $_POST['captcha-token'], 'secretKey' => getenv('YCAPTCHA_SECRET_KEY'), ]), ], ])), true); if (!$response['success']) { http_response_code(400); die('验证码验证失败'); } ``` 重要说明 [#重要说明] * **令牌是一次性的。** 验证成功后,会话会被永久删除。使用相同令牌再次调用 siteverify 将返回 `"Invalid token"`。 * **始终显式检查 `success === true`。** 仅有 200 HTTP 状态码并不意味着成功。 * `secretKey` 通过题目关联到其站点进行验证——一个站点的令牌不能用另一个站点的密钥验证。 # コンセプト (/docs/concepts) データモデル [#データモデル] サイト [#サイト] CAPTCHA がデプロイされるドメインやアプリケーションを表します。各サイトにはクライアント用の **サイトキー** (`pk_...`) とサーバー側検証用の **シークレットキー** (`sk_...`) があります。1 人のユーザーが複数のサイトを所有できます。 画像セット [#画像セット] 名前の付いた画像のプールです (例: "NYC Parks"、"Fire Hydrants")。特定のサイトではなくユーザーに所有されるため、複数のサイトで再利用できます。 画像 [#画像] 画像セット内の 1 枚の画像です。`id`、公開 `url` (Cloudflare R2 にホスト)、オプションの `name` を持ちます。画像自体にはラベルがなく、どの画像が正解かはパズルが定義します。 パズル [#パズル] サイトに属し、画像セットを参照します。以下を含みます: * **プロンプト** — ユーザーに表示されるキーワード (例: "parks")。ウィジェットは固定の指示プレフィックスとともに表示します。 * **正解画像** — セット内のどの画像が正しい答えか * **正解数 (Correct count)** — チャレンジごとに表示する正解画像の枚数 (毎回、正解プールからランダムに選択) * **不正解画像** — オプションの手動選択ディストラクター。未設定の場合、ディストラクターはセット内の残りの画像からランダムに選ばれます * **難易度** — 0 から 1 の小数 (デフォルト 0.5)。合格しきい値を制御します * **有効/無効** — 削除せずにパズルを無効化するためのトグル CAPTCHA セッション [#captcha-セッション] Redis に 5 分間の TTL で保存されます。チャレンジセッションは検証に必要なすべてのデータ (画像の順序、正解、難易度) を保持します。検証済みセッションは合格後に作成され、siteverify によってアトミックに消費されます。 *** 検証フロー [#検証フロー] ``` ブラウザ yCAPTCHA サーバー あなたのバックエンド | | | | 1. /widget/{siteKey} を読み込み | | |--------------------------------->| | | | | | 2. POST /challenge {siteKey} | | |--------------------------------->| | | { sessionToken, prompt, images} | | |<---------------------------------| | | | | | 3. ユーザーが画像を選択 | | | | | | 4. POST /verify | | | {sessionToken, selectedIndices} | |--------------------------------->| | | { success: true, token } | | |<---------------------------------| | | | | | 5. 親ページへ postMessage | | | | | | 6. トークン付きでフォーム送信 | | |-------------------------------------------------->| | | | | | | 7. POST /siteverify | | | {token, secretKey} | | |<------------------------| | | { success: true } | | |------------------------>| | | | | | 8. 許可/拒否 ``` *** スコアリングロジック [#スコアリングロジック] グリッドの組み立て [#グリッドの組み立て] すべてのチャレンジは 3x3 のグリッドで **正確に 9 枚の画像** を提供します。サーバーはパズルの正解プールから `correctCount` 枚の画像をランダムに選択し、残りのスロットをディストラクター (手動選択またはランダム) で埋め、Fisher-Yates でシャッフルします。ウィジェットはプロキシ URL のみを受け取るため、画像 ID を見たり、正解と不正解を区別したりすることはできません。 合格しきい値 [#合格しきい値] 合格に必要な正解選択数は次の通りです: ``` requiredCorrect = Math.ceil(correctCount * difficulty) ``` | correctCount | 難易度 | 必要な選択数 | | ------------ | ---- | ------ | | 3 | 0.5 | 2 | | 3 | 1.0 | 3 | | 3 | 0.25 | 1 | | 5 | 0.5 | 3 | ボット対策ルール [#ボット対策ルール] 9 つのグリッド位置すべてが選択された場合、しきい値チェックの前に **自動的に失敗** します。これは、すべての正解画像にヒットするために全てを選択するボットを捕捉します。 誤選択のペナルティ [#誤選択のペナルティ] 誤った選択ごとにスコアから 1 が差し引かれます。最終スコアは `correctSelections - wrongSelections` となり、これが必要なしきい値以上でなければなりません。これにより、攻撃者が大きなサブセットを選択して十分な正解ヒットを保証することを防ぎます。 *** トークンのライフサイクル [#トークンのライフサイクル] 1. `/challenge` が呼び出されると **チャレンジセッション** が Redis に作成されます (5 分 TTL) 2. `/verify` はチャレンジセッションをアトミックに消費し (リプレイ防止)、Redis に **検証済みセッション** を作成します 3. `/siteverify` は検証済みセッションをアトミックに消費し (一度限りの使用)、シークレットキーを検証します 4. 同じトークンでそれ以降に呼び出すと `"Invalid token"` が返されます # クイックスタート (/docs) はじめに [#はじめに] yCAPTCHA (your-CAPTCHA) は、小さなコミュニティやサブカルチャー向けの CAPTCHA ウィジェットです。汎用の信号機ではなく、コミュニティが共有するネタ——路線図、音ゲー譜面、ニッチなミーム——を素材にして自分でチャレンジを作れます。 **本格的なボット対策サービスではありません**:機械学習も指紋認識も行動分析もありません。本気でボットを防ぎたいなら [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/) や hCaptcha をどうぞ。yCAPTCHA は空気感を大事にします。 API キーの取得 [#api-キーの取得] [ダッシュボード](https://ycaptcha.xyspg.moe/dashboard) でサイトを作成し、API キーを取得します。 シークレットキー (`sk_...`) をクライアントサイドのコードに絶対に公開しないでください。 2 行で埋め込む [#2-行で埋め込む] ```html
``` LLM との連携 [#llm-との連携] ``` https://ycaptcha.xyspg.moe/llms-full.txt ``` 次のステップ [#次のステップ] エンドポイントの完全なドキュメント。 # Create a CAPTCHA challenge (/docs/api-reference/challenge) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new challenge session with a randomized image grid. Called automatically by the widget — you do not need to call this directly. # Fetch a challenge image (/docs/api-reference/image) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Proxies a challenge image from storage without exposing the real URL. Called automatically by the widget — you do not need to call this directly. # 概要 (/docs/api-reference) ベース URL: ``` https://ycaptcha.xyspg.moe ``` 公開エンドポイント [#公開エンドポイント] バックエンドが呼び出す必要がある唯一のエンドポイント: CAPTCHA トークンをサーバー側で検証します。シークレットキーが必要です。 ウィジェット内部エンドポイント [#ウィジェット内部エンドポイント] これらはウィジェットによって自動的に呼び出されます。直接やり取りする必要はありません。 ランダム化された画像グリッドで新しいチャレンジセッションを作成します。 検証のためにユーザーの画像選択を送信します。 実際のストレージ URL を公開せずにチャレンジ画像をプロキシします。 レート制限 [#レート制限] IP ごとのスライディングウィンドウのレート制限が、すべての API ルートに適用されます: | ルート | 制限 | | ------------- | ----------- | | `/siteverify` | 100 リクエスト/分 | | `/challenge` | 20 リクエスト/分 | | `/verify` | 10 リクエスト/分 | | `/image` | 60 リクエスト/分 | # CAPTCHA トークンを検証する (/docs/api-reference/siteverify) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} ユーザーが CAPTCHA チャレンジを完了した後の一度限りのトークンを検証します。これはバックエンドが呼び出す必要がある唯一のエンドポイントです。 **サーバーから呼び出す必要があります — ブラウザからは絶対に呼び出さないでください。** # Submit challenge answer (/docs/api-reference/verify) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Submits the user's image selections for verification. Called automatically by the widget — you do not need to call this directly. # サイトを作成する (/docs/get-started/create-site) ウィジェットを埋め込む前に、yCAPTCHA ダッシュボードでサイトを作成し、サイトキーとシークレットキーを取得する必要があります。 1\. サイトを作成する [#1-サイトを作成する] 1. [yCAPTCHA ダッシュボード](https://ycaptcha.xyspg.moe/dashboard) にログインします。 2. **Sites** に移動し、**Create Site** をクリックします。 3. サイトに名前を付けます (例: "My Blog"、"Login Page")。 4. サイトには 2 つのキーが発行されます: | キー | 形式 | 用途 | | -------- | -------- | ----------------------------- | | サイトキー | `pk_...` | 公開。ウィジェットの iframe URL で使用します。 | | シークレットキー | `sk_...` | 非公開。検証のためにサーバー上でのみ使用します。 | > **シークレットキーをクライアントサイドのコードに絶対に公開しないでください。** サーバー間の API 呼び出しでのみ使用する必要があります。 2\. 画像をアップロードする [#2-画像をアップロードする] 1. ダッシュボードで **Image Sets** に移動します。 2. 新しい画像セットを作成します (例: "NYC Parks"、"Fire Hydrants")。 3. チャレンジで使用する画像をアップロードします。 画像セットは特定のサイトではなくアカウントに所有されます。同じ画像セットを複数のサイトで再利用できます。 3\. パズルを作成する [#3-パズルを作成する] 1. サイトの **Puzzles** セクションに移動します。 2. **Create Puzzle** をクリックして設定します: | 設定項目 | 説明 | | ----- | ------------------------------------------------------- | | 画像セット | このパズルで使用する画像プールを選択します。 | | プロンプト | ユーザーに表示されるキーワード (例: `parks`)。ウィジェットは固定の指示プレフィックスを追加します。 | | 正解画像 | セット内のどの画像が正解かをマークします。 | | 不正解画像 | 任意で手動のディストラクターを指定します。空のままにすると、プールからランダムに選択されます。 | | 難易度 | 0 から 1 の小数 (デフォルト 0.5)。合格に必要な正解選択数を制御します。 | プロンプト [#プロンプト] キーワードのみを入力してください (例: `parks`、`fire hydrants`)。ウィジェットは常に "Select all images with" という指示プレフィックスを表示します。 難易度 [#難易度] 合格しきい値は次のように計算されます: ``` requiredCorrect = Math.ceil(correctImageCount * difficulty) ``` 例えば、正解画像が 3 枚、難易度が 0.5 の場合、ユーザーは少なくとも 2 枚の正解画像を選択する必要があります。 次のステップ [#次のステップ] 少なくとも 1 つのパズルが設定されたサイトを用意したら: 1. Web ページに [ウィジェットを埋め込む](/docs/get-started/embed-widget) 2. サーバーで [トークンを検証する](/docs/get-started/server-validation) # ウィジェットを埋め込む (/docs/get-started/embed-widget) 2 行の HTML で yCAPTCHA をサイトに追加します。スクリプトはすべてを処理します — ウィジェットの作成、チャレンジフローの管理、リサイズ、検証トークン付きの hidden フォーム入力の注入まで行います。 前提条件 [#前提条件] * 少なくとも 1 つの [設定済みパズル](/docs/get-started/create-site) を持つ yCAPTCHA サイト * サイトキー (`pk_...`) *** クイックスタート [#クイックスタート] ```html
``` これだけです。スクリプトはページ上のすべての `.y-captcha` 要素を自動的に見つけ、ウィジェットを作成し、その横に hidden の `` を挿入します。ユーザーがチャレンジに合格すると、input に検証トークンが入力され、フォームと一緒に送信できる状態になります。 *** フォームとの統合 [#フォームとの統合] ```html
``` フォーム送信時、トークンはフォームボディ内で `ycaptcha-response` として送信されます。[サーバー上で検証してください](/docs/get-started/server-validation)。 *** 明示的なレンダリング [#明示的なレンダリング] より細かい制御が必要な場合は、自動レンダリングの代わりに JavaScript API を使用してください: ```html
``` *** JavaScript API [#javascript-api] スクリプトは次のメソッドを持つ `window.ycaptcha` を公開します: `ycaptcha.render(container, params)` [#ycaptcharendercontainer-params] コンテナ要素にウィジェットをレンダリングします。 | パラメータ | 型 | 説明 | | ------------------------------- | ----------------------- | ------------------------------------------------ | | `container` | `string \| HTMLElement` | CSS セレクタまたは DOM 要素 | | `params.sitekey` | `string` | サイトキー (`data-sitekey` を上書き) | | `params.callback` | `function` | 成功時にトークンとともに呼び出されます | | `params["expired-callback"]` | `function` | トークンが期限切れになったときに呼び出されます | | `params["error-callback"]` | `function` | 失敗時にエラーメッセージとともに呼び出されます | | `params["response-field-name"]` | `string` | カスタム hidden input 名 (デフォルト: `ycaptcha-response`) | ウィジェット ID の文字列を返します。 `ycaptcha.getResponse(widgetId?)` [#ycaptchagetresponsewidgetid] 現在の検証トークンを返します。まだ検証されていない場合は `null` を返します。 `ycaptcha.reset(widgetId?)` [#ycaptcharesetwidgetid] ウィジェットを初期状態にリセットします。フォーム送信後にユーザーが再度解けるようにするために使用します。 `ycaptcha.remove(widgetId?)` [#ycaptcharemovewidgetid] ウィジェットを DOM から削除してクリーンアップします。 `ycaptcha.isExpired(widgetId?)` [#ycaptchaisexpiredwidgetid] トークンが期限切れの場合 `true` を返します。 > `widgetId` を省略した場合、すべてのメソッドは直近に作成されたウィジェットに対して動作します。 *** data 属性 [#data-属性] JavaScript API の代わりに、`data-*` 属性でウィジェットを設定することもできます: ```html
``` 属性値は、対応するイベントが発生したときに呼び出されるグローバル関数名です。 *** イベント [#イベント] ウィジェットは内部的に `postMessage` を介して通信します。低レベルアクセスが必要な場合 (例: フレームワーク統合のため)、これらがそのイベントです: | イベント | ペイロード | 説明 | | --------- | ------------------- | ------------------------------ | | `success` | `{ token: string }` | ユーザーが CAPTCHA に合格 | | `expired` | — | 5 分のセッションが経過 | | `error` | `{ code, message }` | 致命的なエラー (無効な siteKey、ネットワーク障害) | | `resize` | `{ width, height }` | ウィジェットの寸法が変更されました | すべてのメッセージには、フィルタリング用の `source: "ycaptcha"` が含まれます。 *** 次のステップ [#次のステップ] ウィジェットを埋め込んだら、[サーバー上でトークンを検証してください](/docs/get-started/server-validation)。 # 概要 (/docs/get-started) このガイドでは、yCAPTCHA ウィジェットをページに埋め込み、そのトークンをサーバーサイドで検証する手順をはじめます。yCAPTCHA はコミュニティの内輪ネタを使うウィジェットであり、ボット検出サービスではありません — 詳しくは [FAQ](/#faq) を参照してください。 前提条件 [#前提条件] はじめる前に、次のものが必要です: * yCAPTCHA アカウント * ウィジェットを追加したい Web サイトまたは Web アプリケーション * HTML とお好みのサーバーサイド言語の基本的な知識 *** 手順 [#手順] yCAPTCHA ウィジェットは、ページ上でユーザーに提示されるチャレンジ画面です。各サイトには固有のキーペアとパズル設定が割り当てられます。 | コンポーネント | 説明 | | -------- | ----------------------------------- | | サイトキー | サイト上でウィジェットを呼び出すための公開キー (`pk_...`)。 | | シークレットキー | サーバーサイドでのトークン検証に使用する秘密鍵 (`sk_...`)。 | | パズル | 設定可能な正解と難易度を備えた画像チャレンジ。 | > サイトとパズルをどのように作成しても、Web ページに [ウィジェットを埋め込む](/docs/get-started/embed-widget) ことと、サーバーで [トークンを検証する](/docs/get-started/server-validation) ことは依然として必要です。 yCAPTCHA の実装には、2 つの不可欠なコンポーネントが含まれます: 1. **クライアントサイド**: [ウィジェットを埋め込む](/docs/get-started/embed-widget) — yCAPTCHA の iframe を Web ページに追加して訪問者にチャレンジを出し、検証トークンを生成します。 2. **サーバーサイド**: [トークンを検証する](/docs/get-started/server-validation) — Siteverify API を使用してサーバー上でトークンを検証し、真正性を確認します。 *** 実装 [#実装] 以下の手順に従って yCAPTCHA を実装してください。 1\. サイトとパズルを作成する [#1-サイトとパズルを作成する] まず、サイトを作成し、キーを取得するために少なくとも 1 つのパズルを設定します。 サイトの作成、画像のアップロード、パズルの設定に関する手順については、[サイトを作成する](/docs/get-started/create-site) を参照してください。 2\. ウィジェットを埋め込む [#2-ウィジェットを埋め込む] yCAPTCHA ウィジェットの iframe を Web ページに追加し、`postMessage` 経由で検証トークンを受信します。 埋め込み、リサイズ処理、イベントの受信については、[ウィジェットを埋め込む](/docs/get-started/embed-widget) を参照してください。 3\. トークンを検証する [#3-トークンを検証する] ウィジェットが生成したトークンを検証するため、サーバーサイドの検証を実装します。 適切なトークン検証で実装を保護する方法については、[サーバーサイド検証](/docs/get-started/server-validation) を参照してください。 *** セキュリティ要件 [#セキュリティ要件] * **サーバーサイド検証は必須です。** すべてのトークンについて Siteverify API を呼び出して検証する必要があります。検証しないと、実装は脆弱なままになります。 * **トークンは 5 分後に期限切れになります。** 各トークンは 1 回のみ検証できます。期限切れまたは使用済みのトークンは、新しいチャレンジで置き換える必要があります。 * **シークレットキーを保護してください。** `sk_...` キーをクライアントサイドのコードに絶対に公開しないでください。 # サーバーサイド検証 (/docs/get-started/server-validation) ユーザーが CAPTCHA を完了すると、`postMessage` 経由で一度限りのトークンを受け取ります。フォーム送信を受け入れる前に、このトークンをサーバー上で検証 **しなければなりません**。 この呼び出しは常にサーバー上で行う必要があります — ブラウザで行ってはいけません。 エンドポイント [#エンドポイント] ``` POST https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify ``` リクエスト [#リクエスト] | フィールド | 型 | 必須 | 説明 | | ----------- | -------- | -- | ------------------------------ | | `token` | `string` | はい | ウィジェットの `postMessage` からのトークン。 | | `secretKey` | `string` | はい | サイトのシークレットキー (`sk_...`)。 | ```json { "token": "aB3dEfGh...64chars", "secretKey": "sk_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456" } ``` レスポンス [#レスポンス] 成功 [#成功] ```json { "success": true } ``` 失敗 [#失敗] すべての失敗は HTTP 200 で `success: false` と `error` フィールドを返します: | エラー | 意味 | | ------------------------------ | ---------------------------------------- | | `"Missing token or secretKey"` | リクエストボディに必須フィールドが欠けています。 | | `"Invalid token"` | トークンが見つかりません — 発行されていない、既に消費された、または期限切れ。 | | `"Invalid secretKey"` | シークレットキーがこのパズルを所有するサイトと一致しません。 | 例 [#例] Node.js (fetch) [#nodejs-fetch] ```js const response = await fetch( "https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token: req.body.captchaToken, secretKey: process.env.YCAPTCHA_SECRET_KEY, }), }, ); const result = await response.json(); if (!result.success) { return res.status(400).json({ error: "CAPTCHA verification failed" }); } // Proceed with form handling ``` Python (requests) [#python-requests] ```python import requests result = requests.post( "https://ycaptcha.xyspg.moe/api/v0/captcha/siteverify", json={ "token": request.form["ycaptcha-response"], "secretKey": os.environ["YCAPTCHA_SECRET_KEY"], }, ).json() if not result.get("success"): abort(400, "CAPTCHA verification failed") ``` 重要な注意事項 [#重要な注意事項] * **トークンは一度限りの使用です。** 検証が成功すると、セッションは完全に削除されます。同じトークンで siteverify を再度呼び出すと、`"Invalid token"` が返されます。 * **常に `success === true` を明示的にチェックしてください。** HTTP 200 ステータスだけでは成功を意味しません。 * `secretKey` はパズルを通じてサイトに結合することで検証されます — あるサイトのトークンを別のサイトのキーで検証することはできません。