En producción · 99.9% uptime v2.0

Detén los bots.
Sin que tus
usuarios se enteren.

Un checkbox. Cero API keys. Cero tracking. Tus usuarios marcan y siguen. Los bots se chocan contra un muro criptográfico que tu servidor valida en una sola línea de código.

100% self-hosted Cero cookies Cero tracking Sin límites
demo.html — Pruébalo ahora
Marca el checkbox. Cero CPU hasta que lo hagas.
0
Retos emitidos hoy
0
Verificados
0
Tokens consumidos
0€
Coste por uso
Cómo funciona

Invisible para humanos.
Imposible para bots.

Tres pasos. Ni uno más. Sin librerías que cargar antes de tiempo, sin scripts de terceros, sin claves que rotar cada año.

01

El usuario marca un checkbox

Hasta ese clic, mCaptcha duerme. Ni un fetch, ni un ciclo de CPU, ni una petición DNS. Tu Lighthouse no se entera de que existe.

02

Reto criptográfico en ~50 ms

El navegador resuelve un Proof-of-Work con SHA-256. Si la IP ha disparado >30 retos en una hora, pasamos a un slider puzzle que ningún bot atraviesa sin tiempo real de humano.

03

Tu backend recibe un token

Un GET a /api/captcha/verify con el token. Si ok=true, es humano y procesas el form. Single-use, 2 minutos de vida. Una línea de código en cualquier lenguaje.

Comparativa

Mismo objetivo.
Mejores consecuencias.

Todo lo que reCAPTCHA hace por ti, también lo hace mCaptcha. Lo que reCAPTCHA hace a tus usuarios, no.

Google reCAPTCHA
mCaptcha Recomendado
Sin registro ni API keys
Cero tracking del usuario
Cero cookies de terceros
Cero CPU hasta la primera interacción
100% self-hosted (sin dependencia externa)
Compatible con código existente de reCAPTCHA
Verificaciones ilimitadas y gratis
Configuración CSP simple
Compleja
Mínima
Peso del widget (gzipped)
~150 KB
~6 KB
Instalación

Dos líneas. Listo.

Sin npm install. Sin dashboard al que registrarse. Sin claves que copiar. Pega el snippet, despliega y cierra el ticket.

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

    <div class="mcaptcha"></div>

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

<script src="https://medel.es/captcha.js" async defer></script>
Cero impacto en el primer pintado El script se carga con async defer. Nada se ejecuta hasta que el usuario marca el checkbox. Tu Core Web Vitals te lo agradece.
Tema claro, oscuro o automático Por defecto sigue prefers-color-scheme del navegador. Fuérzalo con data-theme="dark|light" en el div del widget.
Drop-in para código de reCAPTCHA Acepta class="g-recaptcha" y rellena g-recaptcha-response. Migra sin tocar una sola línea de tu backend.
Móvil y táctil nativos El slider puzzle responde a dedos como un nativo. Sin selectores oscuros de bicicletas en pantallas de 4 pulgadas.
Verificación

Antes de procesar el formulario,
valida el token.

Tu backend recibe mcaptcha_token. Hace un GET, mira si ok=true, y sigue. Cinco lenguajes, mismo flujo, ni una librería que instalar.

<?php
function mcaptcha($token) {
    $url = 'https://medel.es/api/captcha/verify?token=' . urlencode($token);
    $response = json_decode(file_get_contents($url));
    return $response->ok ?? false;
}

// Uso:
$token = $_POST['mcaptcha_token'] ?? '';

if (!mcaptcha($token)) {
    http_response_code(400);
    exit('Captcha fallido');
}

// ✅ Captcha verificado. Procesa el formulario.
// Validación desde el cliente — útil en SPAs antes de enviar al backend.
// Para protección real, verifica SIEMPRE también en tu servidor.
async function mcaptcha(token) {
    const url = 'https://medel.es/api/captcha/verify?token=' + encodeURIComponent(token);
    const data = await fetch(url).then(r => r.json());
    return data.ok === true;
}

// Uso en un formulario:
document.querySelector('form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const fd = new FormData(e.target);
    const token = fd.get('mcaptcha_token');

    if (!(await mcaptcha(token))) {
        alert('Captcha fallido');
        return;
    }

    // ✅ Captcha verificado. Envía al backend.
    await fetch('/api/contact', { method: 'POST', body: fd });
});
async function mcaptcha(token) {
    const url = 'https://medel.es/api/captcha/verify?token=' + encodeURIComponent(token);
    const data = await fetch(url).then(r => r.json());
    return data.ok === true;
}

// Uso (Express):
const token = req.body.mcaptcha_token;

if (!(await mcaptcha(token))) {
    return res.status(400).send('Captcha fallido');
}

// ✅ Captcha verificado. Procesa el formulario.
import urllib.request, urllib.parse, json

def mcaptcha(token):
    url = 'https://medel.es/api/captcha/verify?' + urllib.parse.urlencode({'token': token})
    with urllib.request.urlopen(url, timeout=5) as r:
        return json.loads(r.read()).get('ok') is True

# Uso (Flask):
token = request.form.get('mcaptcha_token', '')

if not mcaptcha(token):
    abort(400, 'Captcha fallido')

# ✅ Captcha verificado. Procesa el formulario.
# Solo necesitas un GET con el token como query param:
curl 'https://medel.es/api/captcha/verify?token=YOUR_TOKEN'

# OK:   {"ok":true,"verified_at":"2026-06-22 12:24:56"}
# FAIL: {"ok":false,"error":"already_used"}
¿Migras desde Google reCAPTCHA?

El widget también rellena g-recaptcha-response y acepta class="g-recaptcha". Si tu backend ya lo lee, sólo cambia la URL de verify:

// Antes:
$url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $secret . '&response=' . $token;

// Después (sin secret):
$url = 'https://medel.es/api/captcha/verify?response=' . $token;
API

Endpoints

Cuatro endpoints REST. Sin SDK. Sin auth. Sin versionado. Solo el que necesitas y los demás los llama el widget.

GET https://medel.es/api/captcha/verify?token=… Lo llamas tú

Verifica el token. Single-use: tras el primer verify exitoso queda invalidado, así nadie lo replay-ataca.

200 OK:
{"ok": true, "verified_at": "2026-06-22 12:24:56"}
400 KO:
{"ok": false, "error": "already_used"}

Códigos: missing_token · unknown_token · already_used · expired · origin_mismatch

GET https://medel.es/api/captcha/challenge Lo llama el widget

Emite un reto (PoW por defecto, slider puzzle si la IP es sospechosa). El widget lo llama solo.

POST https://medel.es/api/captcha/solve Lo llama el widget

El widget envía la solución (nonce del PoW o posición X del slider) y recibe el token verificable.

GET https://medel.es/api/captcha/stats Público

Estadísticas agregadas del día. Cero datos personales, cero IPs, cero tracking.

Compatibilidad

Funciona en el 98%
sin tocar nada.

Funciona automáticamente en

  • Sitios HTTPS
  • Chrome / Edge / Firefox / Safari (2017+)
  • localhost en desarrollo
  • SPA (React / Vue / Svelte)
  • Formularios server-rendered

Si tienes CSP estricta

Añade el dominio a 3 directivas:

script-src  'self' https://medel.es;
connect-src 'self' https://medel.es;
style-src   'self' https://medel.es;
FAQ

Preguntas honestas, respuestas honestas

¿Cuándo aparece el slider puzzle?
Sólo si tu IP ha disparado más de 30 verificaciones en la última hora. Para el 99% de tus usuarios el flujo termina en un solo clic.
¿Es gratis para siempre?
Sí. Sin tarjeta, sin plan, sin upsells. El servicio se sostiene con rate-limit por IP (100/h) y por dominio (5000/h). Si necesitas más, escríbenos y lo subimos.
¿Funciona sin JavaScript?
No. mCaptcha necesita JS para resolver el Proof-of-Work o arrastrar el slider. Si esperas tráfico sin JS, deja un honeypot HTML como fallback.
¿Hace tracking de mis usuarios?
No. Cero cookies, cero fingerprinting, cero analytics. Sólo guardamos la IP durante 24 horas para rate-limit. Después, se borra automáticamente.
¿Por qué un slider en vez de "selecciona los semáforos"?
Las imágenes tipo reCAPTCHA requieren un dataset gigante etiquetado y ML por encima. Un slider es visualmente equivalente, no requiere banco de fotos y funciona en cualquier dispositivo, incluido el móvil más viejo.
¿Y si Medel Captcha se cae?
El widget muestra error y bloquea el envío por defecto. Como integrador puedes decidir bypass en tu backend si el verify timeout (con 5 segundos sobra). 99.9% uptime, pero el plan B es tuyo.
¿Es compatible con reCAPTCHA?
Sí. El widget rellena automáticamente g-recaptcha-response y acepta class="g-recaptcha". Migrar es cambiar el src del script y la URL de verify. Cero refactor.
¿Cómo lo paro si quiero auto-hostarlo?
Es PHP 8.2+ con MySQL. El código vivirá en GitHub bajo MIT en cuanto haga limpieza. Mientras tanto, escríbeme y te lo paso.

Pega el snippet.
Olvídate de los bots.

Integra en 30 segundos. Sin tarjeta. Sin claves. Sin contratos. Sin letra pequeña. Cuando un bot intente entrar, será problema suyo, no tuyo.