Home Medel Captcha
Service status: operational

CAPTCHA, free,
no keys, zero friction.

Protect your forms against bots in 30 seconds. No signup, no tracking, no quotas. Self-hosted by Medel Platforms, based on asymmetric Proof-of-Work.

↑ Lazy: the widget does NOT use CPU until you click the input. Try it.
51
Challenges issued today
11
Verifications completed
5
Tokens consumed
How it works

Invisible for humans. Expensive for bots.

1

The server issues a challenge

When the widget loads, it asks Medel Captcha for a challenge: a random seed + a difficulty level. Server cost: 1 INSERT + 2 ms.

2

The browser solves a PoW

In a WebWorker (without blocking the UI), the browser searches for a nonce such that sha256(seed + nonce) starts with N zeros. For a user it takes ~0.5-2s. For a bot at scale, it multiplies its CPU cost.

3

Single-use token, 2 minutes

The server verifies the PoW and issues a single-use token, valid for 2 minutes. Your backend verifies it with one call before processing the form.

Installation

2 lines of HTML.

Copy the snippet and paste it into your form. No npm, no keys, no config.

HTML
<form action="/contact" method="POST">
    <input name="email" required>
    <textarea name="message" required></textarea>

    <div class="medel-captcha"></div>

    <button type="submit">Send</button>
</form>

<script src="https://medel.es/captcha.js" async defer></script>
Lazy by default 0 ms CPU until the user touches the form.
No sitekey, no secret Paste the script and done. Like reCAPTCHA but no signups.
reCAPTCHA drop-in Also accepts class="g-recaptcha" and fills g-recaptcha-response. Migrate without touching your backend.
Server-side verification

Before processing the form, verify the token.

On your server, when receiving the POST from the form, call the verification endpoint with the token that came in the medel_captcha_token field. The token is single-use: it is invalidated after the first call.

<?php
$token = $_POST['medel_captcha_token'] ?? '';

$ch = curl_init('https://medel.es/api/captcha/verify');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode(['token' => $token]),
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);

if (empty($res['ok'])) {
    http_response_code(400);
    exit('Captcha verification failed: ' . ($res['error'] ?? 'unknown'));
}

// ✅ Captcha verificado. Procede a procesar el formulario.
const token = req.body.medel_captcha_token;

const res = await fetch('https://medel.es/api/captcha/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token }),
});
const data = await res.json();

if (!data.ok) {
    return res.status(400).json({ error: 'Captcha failed', reason: data.error });
}

// ✅ Captcha verificado. Procede a procesar el formulario.
import requests

token = request.form.get('medel_captcha_token', '')

res = requests.post(
    'https://medel.es/api/captcha/verify',
    json={'token': token},
    timeout=5,
)
data = res.json()

if not data.get('ok'):
    abort(400, f"Captcha failed: {data.get('error')}")

# ✅ Captcha verificado. Procede a procesar el formulario.
curl -X POST 'https://medel.es/api/captcha/verify' \
  -H 'Content-Type: application/json' \
  -d '{"token":"EL_TOKEN_DEL_FORM"}'

# Respuesta OK:
# {"ok":true,"verified_at":"2026-06-22 12:24:56"}

# Respuesta error:
# {"ok":false,"error":"already_used"}
API

Complete reference

GET https://medel.es/api/captcha/challenge

Issues a new Proof-of-Work challenge. The widget calls it on load; your server does not need it directly.

Response 200:
{
  "ok": true,
  "challenge_token": "21091047befb5f136b6c6525119b8eb0",
  "seed": "7bb242e980f302742c743370955afef1",
  "difficulty": 4,
  "expires_in": 300
}
Common errors:
  • banned — la IP está baneada 24h por abuso.
  • rate_limit_ip — más de 100 challenges/hora desde esta IP.
  • rate_limit_origin — más de 5000/hora desde tu dominio.
POST https://medel.es/api/captcha/solve

The widget sends the found nonce. The widget calls it; your server does not need it.

JSON body:
{
  "challenge_token": "21091047befb5f136b6c6525119b8eb0",
  "nonce": "1841"
}
Response 200:
{
  "ok": true,
  "token": "391faafa6b881fc4e174d046fd2eb9f6",
  "expires_in": 120
}
Compatibility

Works on 98% of sites without touching anything.

Works automatically in:

  • Any site served over HTTPS (required by the WebCrypto API).
  • Modern browsers: Chrome/Edge 60+, Firefox 60+, Safari 11+ (released since 2017).
  • localhost during development (browsers treat localhost as a secure context).
  • Sites with moderate CSP that allow third-party scripts.
  • Traditional server-rendered forms AND SPAs (React/Vue/Svelte) — the script detects dynamically added widgets.

If your site has strict Content Security Policy

Add these minimum directives to your CSP. No changes needed in your backend or rest of the HTML:

Content-Security-Policy:
  script-src  'self' https://medel.es;
  connect-src 'self' https://medel.es;
  style-src   'self' https://medel.es;

The widget does NOT use WebWorkers (common CSP-paranoia), does NOT inject inline <style> (CSS from external file), and does NOT require unsafe-eval. Zero conflicts with serious security policies.

Will NOT work in these (edge) cases:

  • Pure HTTP sites (no TLS). WebCrypto requires secure context. Solution: use HTTPS — your site needs it in 2026 anyway.
  • JavaScript disabled. Limitation of any modern captcha. As fallback, keep a pure HTML honeypot.
  • Aggressive blockers. Some extensions could block third-party scripts. Rare for captchas (usually allowlisted).
Privacy

What we do NOT store.

No cookies.
No browser fingerprinting.
No data selling or sharing.
We do store the client IP for 24h, only for rate-limit and automatic ban against abuse. Purged afterwards.
We do log the domain (Origin/Referer) of the request for aggregated stats. Not associated with concrete IPs.
FAQ

Frequently asked questions

Is it really free and without keys?
Yes. No signup, no email, no card. Anyone can integrate it by copying the snippet. Limits are by IP and domain, not by account.
How do you prevent service abuse?
Rate limit by IP (100/h) and by domain (5000/h) in a sliding window. If an IP exceeds 10x the limit, it is automatically banned for 24h. No additional requirement on the integrator.
Does it work without JavaScript?
No. Medel Captcha requires JS because the Proof-of-Work is solved in the browser. For non-JS users, we recommend a pure HTML honeypot fallback in your form.
Why Proof-of-Work and not a puzzle?
PoW is invisible to the user (no action required). Puzzles like "select the traffic lights" are expensive to generate and annoying. PoW is mathematical and ML-resistant.
Can a bot solve the PoW?
Yes, but it costs. Individually trivial; at scale (10k bots trying) it consumes expensive CPU. The system raises difficulty automatically when it detects suspicion from an IP.
Can I use it in AJAX forms?
Yes. The medel_captcha_token field is filled automatically in the form. If you submit via fetch/axios, the field travels with the rest of the FormData.
Does it work in iOS/Safari/Firefox?
Yes. It uses WebCrypto API (native SHA-256) and WebWorkers, supported in all modern browsers since 2018.
What if Medel Captcha goes down?
The widget shows "Could not verify" and blocks submit by default. As an integrator, you can decide bypass or not in your backend if verify fails by timeout.

Paste the snippet and forget about bots.

Start now — it is free