natbox.io

CaptchaBox / Docs / Flask

Add a captcha to Flask

Last updated 6 November 2026 · By Mohammad AlSaraf

About 5 minutes. Flask 2 or 3; the check uses only the Python standard library.

You will add a decorator, @captchabox_required, that refuses a POST unless CaptchaBox confirms the token.

1. Get your keys

Sign in to the CaptchaBox client area (a free account is enough) and add your site with its domain. You get a site key for the page and a secret for your server. Keep the secret out of your HTML and out of git: put it in an environment variable.

2. The check and the decorator

def verify_captchabox(token):
    """Asks CaptchaBox whether the token from the form is valid. Each token works once, for 5 minutes."""
    if not token:
        return False
    data = urllib.parse.urlencode({"secret": SECRET, "response": token}).encode()
    try:
        with urllib.request.urlopen("https://captcha.natbox.io/v1/siteverify", data, timeout=10) as r:
            return json.load(r).get("success") is True
    except OSError:
        return False  # could not reach CaptchaBox


def captchabox_required(view):
    """Decorator for POST views that take a form."""
    @wraps(view)
    def wrapper(*args, **kwargs):
        if request.method == "POST" and not verify_captchabox(request.form.get("captchabox-response", "")):
            abort(400, "The captcha check failed. Please go back and try again.")
        return view(*args, **kwargs)
    return wrapper

3. The widget in your template

<script src="https://captcha.natbox.io/v1/widget.js" async defer></script>
<form method="post">
  <input name="email" type="email" required>
  <div class="captchabox" data-sitekey="{{ site_key }}"></div>
  <button>Send</button>
</form>

Using Flask-WTF? Keep its CSRF token in the form as usual; the widget only adds the hidden captchabox-response field.

The complete example

Run it with flask --app app run:

"""CaptchaBox example: Flask.
Run: pip install flask && CAPTCHABOX_SITE_KEY=... CAPTCHABOX_SECRET=... flask --app app run
Test keys that always pass, on any domain: cb_test_pass / cbs_test_pass
"""
import json
import os
import urllib.parse
import urllib.request
from functools import wraps

from flask import Flask, abort, render_template_string, request

SITE_KEY = os.environ.get("CAPTCHABOX_SITE_KEY", "cb_test_pass")
SECRET = os.environ.get("CAPTCHABOX_SECRET", "cbs_test_pass")

app = Flask(__name__)


def verify_captchabox(token):
    """Asks CaptchaBox whether the token from the form is valid. Each token works once, for 5 minutes."""
    if not token:
        return False
    data = urllib.parse.urlencode({"secret": SECRET, "response": token}).encode()
    try:
        with urllib.request.urlopen("https://captcha.natbox.io/v1/siteverify", data, timeout=10) as r:
            return json.load(r).get("success") is True
    except OSError:
        return False  # could not reach CaptchaBox


def captchabox_required(view):
    """Decorator for POST views that take a form."""
    @wraps(view)
    def wrapper(*args, **kwargs):
        if request.method == "POST" and not verify_captchabox(request.form.get("captchabox-response", "")):
            abort(400, "The captcha check failed. Please go back and try again.")
        return view(*args, **kwargs)
    return wrapper


PAGE = """<!doctype html>
<meta charset="utf-8">
<title>Contact</title>
<script src="https://captcha.natbox.io/v1/widget.js" async defer></script>
{% if sent %}<p id="result">Thanks, your message was sent.</p>{% endif %}
<form method="post">
  <input name="email" type="email" placeholder="Your email" required>
  <div class="captchabox" data-sitekey="{{ site_key }}"></div>
  <button>Send</button>
</form>"""


@app.route("/", methods=["GET", "POST"])
@captchabox_required
def contact():
    sent = request.method == "POST"  # the visitor passed: send the email, save the message, etc.
    return render_template_string(PAGE, site_key=SITE_KEY, sent=sent)

Trying it out? The test keys cb_test_pass (site key) and cbs_test_pass (secret) always pass, on any domain including localhost. Swap in your own keys from the client area before going live.

Other platforms: all CaptchaBox docs. Questions? Open a ticket, we reply within 24 hours.