Django applications frequently need to handle CAPTCHAs in two scenarios: verifying CAPTCHAs on your own forms (protecting against bots) and solving CAPTCHAs on external sites (data collection, testing, automation). This guide covers both patterns using CaptchaAI.
Scenario 1: Verifying CAPTCHAs on your Django forms
When you add Turnstile or reCAPTCHA to your Django forms, you need to verify tokens server-side.
Adding Turnstile to a Django form
# forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
cf_turnstile_response = forms.CharField(
widget=forms.HiddenInput(),
required=True,
)
# views.py
import requests
from django.conf import settings
from django.shortcuts import render, redirect
from .forms import ContactForm
def contact_view(request):
if request.method == "POST":
form = ContactForm(request.POST)
if form.is_valid():
# Verify Turnstile token with Cloudflare
token = form.cleaned_data["cf_turnstile_response"]
verification = requests.post(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
data={
"secret": settings.TURNSTILE_SECRET_KEY,
"response": token,
"remoteip": request.META.get("REMOTE_ADDR"),
},
).json()
if verification.get("success"):
# Process the form
return redirect("success")
else:
form.add_error(None, "CAPTCHA verification failed")
else:
form = ContactForm()
return render(request, "contact.html", {
"form": form,
"turnstile_sitekey": settings.TURNSTILE_SITE_KEY,
})
<!-- templates/contact.html -->
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="cf-turnstile" data-sitekey="{{ turnstile_sitekey }}"></div>
<button type="submit">Send</button>
</form>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
Scenario 2: Solving CAPTCHAs on external sites
This is where CaptchaAI comes in — when your Django app needs to interact with CAPTCHA-protected external sites.
CaptchaAI service class
# services/captcha_solver.py
import time
import requests
from django.conf import settings
class CaptchaSolverService:
"""Django service for solving CAPTCHAs via CaptchaAI."""
API_BASE = "https://ocr.captchaai.com"
def __init__(self):
self.api_key = settings.CAPTCHAAI_API_KEY
def solve_recaptcha_v2(self, sitekey, page_url, invisible=False):
"""Solve reCAPTCHA v2."""
params = {
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": 1,
}
if invisible:
params["invisible"] = 1
return self._submit_and_poll(params)
def solve_turnstile(self, sitekey, page_url, action=None):
"""Solve Cloudflare Turnstile."""
params = {
"key": self.api_key,
"method": "turnstile",
"sitekey": sitekey,
"pageurl": page_url,
"json": 1,
}
if action:
params["action"] = action
return self._submit_and_poll(params)
def solve_image(self, image_base64):
"""Solve image/text CAPTCHA."""
return self._submit_and_poll({
"key": self.api_key,
"method": "base64",
"body": image_base64,
"json": 1,
})
def get_balance(self):
"""Check API balance."""
response = requests.get(f"{self.API_BASE}/res.php", params={
"key": self.api_key,
"action": "getbalance",
"json": 1,
}, timeout=30)
return float(response.json().get("request", 0))
def _submit_and_poll(self, params, timeout=120):
"""Submit task and poll for result."""
# Submit
response = requests.post(f"{self.API_BASE}/in.php", data=params, timeout=30)
response.raise_for_status()
data = response.json()
if data.get("status") != 1:
raise CaptchaSolveError(f"Submit failed: {data.get('request')}")
task_id = data["request"]
# Poll
start = time.time()
while time.time() - start < timeout:
time.sleep(5)
result = requests.get(f"{self.API_BASE}/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1,
}, timeout=30).json()
if result.get("status") == 1:
return result["request"]
if result.get("request") == "ERROR_CAPTCHA_UNSOLVABLE":
raise CaptchaSolveError("CAPTCHA unsolvable")
raise CaptchaSolveError("Solve timed out")
class CaptchaSolveError(Exception):
pass
Django settings
# settings.py
CAPTCHAAI_API_KEY = "YOUR_API_KEY"
TURNSTILE_SITE_KEY = "0x4AAAAAAAC3DHQhMMQ_Rxrg"
TURNSTILE_SECRET_KEY = "0x4AAAAAAAC3DHQhYYY_secret"
Using the service in views
View for external data collection
# views.py
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from .services.captcha_solver import CaptchaSolverService, CaptchaSolveError
@require_POST
def scrape_external_data(request):
"""Solve CAPTCHA and fetch data from external CAPTCHA-protected site."""
url = request.POST.get("target_url")
if not url:
return JsonResponse({"error": "target_url required"}, status=400)
solver = CaptchaSolverService()
try:
# Solve the CAPTCHA
token = solver.solve_turnstile(
sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg",
page_url=url,
)
# Use token to access the protected resource
import requests as http_requests
response = http_requests.post(url, data={
"cf-turnstile-response": token,
}, timeout=30)
return JsonResponse({
"status": "success",
"data": response.text[:1000],
})
except CaptchaSolveError as e:
return JsonResponse({"error": str(e)}, status=500)
Django management command
# management/commands/solve_captcha.py
from django.core.management.base import BaseCommand
from myapp.services.captcha_solver import CaptchaSolverService
class Command(BaseCommand):
help = "Solve a CAPTCHA and print the token"
def add_arguments(self, parser):
parser.add_argument("--type", choices=["recaptcha", "turnstile"], required=True)
parser.add_argument("--sitekey", required=True)
parser.add_argument("--url", required=True)
def handle(self, *args, **options):
solver = CaptchaSolverService()
self.stdout.write(f"Solving {options['type']} for {options['url']}...")
if options["type"] == "recaptcha":
token = solver.solve_recaptcha_v2(options["sitekey"], options["url"])
else:
token = solver.solve_turnstile(options["sitekey"], options["url"])
self.stdout.write(self.style.SUCCESS(f"Token: {token[:50]}..."))
# Check balance
balance = solver.get_balance()
self.stdout.write(f"Remaining balance: ${balance:.2f}")
Usage:
python manage.py solve_captcha --type turnstile --sitekey 0x4AAA... --url https://example.com
Async Django with CaptchaAI
Django 4.1+ supports async views:
# views.py (async)
import aiohttp
import asyncio
from django.http import JsonResponse
CAPTCHAAI_API_KEY = "YOUR_API_KEY"
async def solve_captcha_async(request):
"""Async view for solving CAPTCHAs."""
sitekey = request.GET.get("sitekey")
page_url = request.GET.get("url")
if not sitekey or not page_url:
return JsonResponse({"error": "sitekey and url required"}, status=400)
async with aiohttp.ClientSession() as session:
# Submit
async with session.post("https://ocr.captchaai.com/in.php", data={
"key": CAPTCHAAI_API_KEY,
"method": "turnstile",
"sitekey": sitekey,
"pageurl": page_url,
"json": 1,
}) as resp:
data = await resp.json()
if data.get("status") != 1:
return JsonResponse({"error": data.get("request")}, status=500)
task_id = data["request"]
# Poll
for _ in range(30):
await asyncio.sleep(5)
async with session.get("https://ocr.captchaai.com/res.php", params={
"key": CAPTCHAAI_API_KEY,
"action": "get",
"id": task_id,
"json": 1,
}) as resp:
result = await resp.json()
if result.get("status") == 1:
return JsonResponse({"token": result["request"]})
return JsonResponse({"error": "timeout"}, status=504)
Celery integration for background solving
For long-running CAPTCHA solves, use Celery:
# tasks.py
from celery import shared_task
from .services.captcha_solver import CaptchaSolverService, CaptchaSolveError
@shared_task(bind=True, max_retries=2, default_retry_delay=10)
def solve_captcha_task(self, captcha_type, sitekey, page_url):
"""Background CAPTCHA solving with Celery."""
solver = CaptchaSolverService()
try:
if captcha_type == "recaptcha_v2":
token = solver.solve_recaptcha_v2(sitekey, page_url)
elif captcha_type == "turnstile":
token = solver.solve_turnstile(sitekey, page_url)
else:
raise ValueError(f"Unknown type: {captcha_type}")
return {"success": True, "token": token}
except CaptchaSolveError as e:
self.retry(exc=e)
# Usage in views
from .tasks import solve_captcha_task
def start_solve(request):
result = solve_captcha_task.delay("turnstile", "0x4AAA...", "https://example.com")
return JsonResponse({"task_id": result.id})
def check_solve(request, task_id):
from celery.result import AsyncResult
result = AsyncResult(task_id)
if result.ready():
return JsonResponse(result.get())
return JsonResponse({"status": "pending"})
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
CaptchaSolveError in production |
API key not in settings | Add CAPTCHAAI_API_KEY to Django settings |
| Celery task retries endlessly | Unsolvable CAPTCHA or wrong sitekey | Set max_retries and validate input |
| Async view hangs | Sync code in async view | Use aiohttp instead of requests |
| Token expired before form submit | Solve took too long | Solve just-in-time, not ahead |
| Import errors in management command | Service not in INSTALLED_APPS | Check app registration |
Frequently asked questions
Should CAPTCHA solving be synchronous or async?
Use Celery for web-facing views so the user doesn't wait 15+ seconds. Use synchronous solving in management commands and background scripts.
How do I store API keys securely?
Use environment variables or Django's django-environ package. Never commit API keys to version control.
Can I cache solved tokens?
reCAPTCHA tokens expire in 120 seconds and Turnstile tokens in 300 seconds. Caching is not practical — solve just before use.
Should I create one service instance or use a singleton?
The CaptchaSolverService class is stateless. Create a new instance per request or use Django's dependency injection patterns.
Summary
Django applications integrate with CaptchaAI through a service class that wraps the submit/poll flow. Use synchronous solving in management commands, async solving in Django 4.1+ async views, and Celery tasks for background processing. The same service handles reCAPTCHA, Turnstile, and image CAPTCHAs.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.