CaptchaBox / Docs
CaptchaBox documentation
Last updated 6 November 2026 · By Mohammad AlSaraf
Guides for your platform
- WordPress: the plugin protects login, registration, comments, WooCommerce and Contact Form 7. No code.
- PHP: a contact form in one file.
- Laravel: a validation rule.
- Node.js and Express: a middleware.
- Next.js: a React component and a Server Action.
- Flask: a decorator.
- Django: a form field.
- Switching from reCAPTCHA, hCaptcha or Turnstile.
Quick start
1. Get keys. Sign in to the client area (a free account is enough) and add your site and its domain. You get a site key, which is public, and a secret, which stays on your server. Subdomains work automatically. To try things first, use the test keys.
2. Add the widget inside your form:
<script src="https://captcha.natbox.io/v1/widget.js" async defer></script>
<form action="https://natbox.io/contact" method="post">
<input name="email" type="email">
<div class="captchabox" data-sitekey="YOUR_SITE_KEY"></div>
<button>Send</button>
</form>
The widget starts its check as soon as the page loads, and puts a token in a hidden field called captchabox-response. If the visitor presses Send before the check is done, the form waits and then sends itself.
3. Check the token on your server before you accept the form:
POST https://captcha.natbox.io/v1/siteverify
Content-Type: application/x-www-form-urlencoded
secret=YOUR_SECRET&response=TOKEN_FROM_THE_FORM
Accept the form only if the answer has "success": true. Never skip this step: the hidden field alone proves nothing.
The siteverify API
Send secret and response as a form (application/x-www-form-urlencoded) or as JSON. The answer always comes with HTTP 200, like reCAPTCHA's and Turnstile's:
{"success": true, "challenge_ts": "2026-11-06T10:15:02Z", "hostname": "example.com", "error-codes": []}
| Field | Meaning |
|---|---|
success | true if the token is valid, unused, under 5 minutes old and belongs to this secret's site. |
challenge_ts | When the visitor's puzzle was issued (UTC). |
hostname | The site the visitor solved it on. Check it if one key serves several domains. |
error-codes | Why it failed: missing-input-secret, invalid-input-secret, missing-input-response, invalid-input-response, timeout-or-duplicate (expired or already used), rate-limited. |
Each token works once and for 5 minutes. The widget gets a fresh one by itself before the old one expires. If your page sends the form with JavaScript and stays open, call captchabox.reset() after each send.
Widget options
| Attribute | Values | What it does |
|---|---|---|
data-sitekey | your site key | Required. |
data-theme | auto, light, dark | auto (default) follows the visitor's system setting. |
data-size | normal, compact, invisible | invisible shows only a small badge. |
data-start | auto, focus, click | When the check starts: on page load (default), when the visitor first uses the form, or when they click the box. |
data-lang | auto, en, ar, es, de, fr, pt, tr | auto uses the page's language. |
data-compat | recaptcha, turnstile | Also fills g-recaptcha-response or cf-turnstile-response, for code written for those services. |
data-response-field-name | a field name | Use another name than captchabox-response. |
data-callback, data-expired-callback, data-error-callback | global function names | Called with the token, when it expires, or with an error code. |
JavaScript API
| Call | What it does |
|---|---|
captchabox.render(element, {sitekey, theme, size, start, callback}) | Puts a widget in an element you choose, for pages built with JavaScript. Returns the widget's id. |
captchabox.getResponse(id) | The current token, or an empty string. |
await captchabox.execute(id) | Starts the check if needed and resolves with a token. Handy for forms sent with fetch. |
captchabox.reset(id) | Throws away the token and starts again. |
The id can be the number from render, the element itself, or a CSS selector. Leave it out to use the first widget on the page. To call render as soon as the script is ready, load it as widget.js?onload=myFunction.
const token = await captchabox.execute();
await fetch('/api/contact', { method: 'POST', body: JSON.stringify({ email, token }) });
captchabox.reset();
Test keys
These work on any domain, including localhost, and do not count against your limit:
| Site key | Secret | siteverify answers |
|---|---|---|
cb_test_pass | cbs_test_pass | always success: true |
cb_test_fail | cbs_test_fail | always success: false |
Widget error codes
Passed to data-error-callback: domain-not-allowed (add the domain to the site in the client area), invalid-sitekey, quota-exceeded (the month's limit is reached), net (CaptchaBox could not be reached) and not-solved.
Limits and plans
Free: 10,000 successful checks a month and up to 10 sites. Plus: 100,000 a month for $2. Pro: 1,000,000 a month for $9. Counts start again on the 1st of each month (UTC). Details on the CaptchaBox page.
Privacy
The widget sets no cookies and stores nothing in the browser. Visitor IP addresses are kept in memory for at most 10 minutes, to limit abuse, and never logged. Details in our privacy policy. Most sites can list CaptchaBox under "spam protection" in their own privacy policy without a consent banner.