CrafyCAPTCHA Documentación

Bienvenido a la documentación oficial de CrafyCAPTCHA, una plataforma avanzada y modular de protección contra bots, ataques DDoS de tráfico masivo y spam.

💡 Privacidad y UX por Diseño

A diferencia de los captchas tradicionales, CrafyCAPTCHA emplea un modelo de fricción adaptativa. Los usuarios legítimos no ven nada o solo un check, mientras que el tráfico sospechoso enfrenta retos biométricos y criptográficos escalonadas.

Propuesta de Valor

Primeros Pasos

Comenzar a proteger tu sitio con CrafyCAPTCHA es rápido y sencillo. Sigue estos pasos para tu primera integración:

  1. Crear una cuenta: Necesitarás acceder con una cuenta de Crafy Account. Puedes usar la que ya tienes o crear una nueva si aún no la posees. Inicia sesión en nuestro Panel de Control.
  2. Añadir Dominios: En el panel, dirígete a la sección Mis Sitios y añade los dominios que deseas proteger.
    Nota: El dominio localhost viene autorizado por defecto para que puedas probar tu integración localmente sin problemas.
  3. Crear una Credencial: En la sección Credenciales, haz clic en Nueva Credencial. Rellena la información solicitada:
    • Nombre (Etiqueta): Un identificador para tu llave (ej. Producción o Desarrollo).
    • Plan: Elige el plan que mejor se adapte a tu volumen de tráfico.
  4. Guardar tu Secret Key: Al crear la credencial, se te proporcionarán las claves necesarias para tu integración. Importante: la Secret Key solo se te mostrará una vez. Guárdala en un lugar seguro (como un archivo .env).
  5. Iniciar Integración: Una vez tengas tus credenciales, estarás listo para comenzar. Dirígete a la sección de El Flujo de Integración para conectar tu Backend y Frontend.

Arquitectura del Sistema

El sistema se divide en capas que analizan y mitigan intentos de acceso malicioso:

1. El Motor de Desafíos (IA y Análisis de Riesgo)

Evalúa una matriz de riesgo sobre el usuario para determinar un Risk Score (entre 0.0 y 1.0). Analiza la reputación de la IP, geolocalización, uso de Tor o DataCenters, y comportamiento de Headers/User-Agents.

2. Validaciones Multi-Capa

Credenciales

Para integrar CrafyCAPTCHA necesitas 4 credenciales. Las 3 primeras (Public Key, Secret Key y Signing Public Key) se obtienen desde tu Panel de Control. El Public Token se obtiene dinámicamente desde el SDK Backend mediante el método getPublicToken().

Credencial Uso Exposición ¿Cambia?
Public Key Identifica tu cuenta. Se usa tanto en el Backend (SDKs) como en el Frontend (SDK Client-Side). Client-Side + Server-Side Nunca
Secret Key Clave secreta para operaciones criptográficas del servidor (crear flows, verificar tokens). Solo se muestra una vez al crear la credencial. Solo Server-Side Nunca
Signing Public Key Clave pública criptográfica usada por el SDK Client-Side para verificar firmas del iframe. Client-Side + Server-Side Nunca
Public Token Token público que identifica el plan activo de tu credencial. Se envía al iframe como parámetro. Client-Side + Server-Side (al cambiar de plan)
⚠️ Secret Key: exposición única

La Secret Key solo se muestra una vez en el momento de crear la credencial. Si la pierdes, deberás generar una nueva credencial. Por seguridad, nunca la expongas en código del lado del cliente (HTML, JavaScript público, repositorios, etc.).

💡 Public Token y cambio de plan

Cuando tu credencial cambia de plan (upgrade o downgrade), el Public Token se regenera automáticamente. Los SDKs Backend gestionan este cambio de forma dinámica: utiliza el método getPublicToken() para obtener siempre el valor actualizado y pasárselo al SDK Client-Side. Las demás credenciales permanecen iguales.

Buenas prácticas: almacenamiento seguro

Se recomienda centralizar todas las credenciales en un archivo de variables de entorno, fuera del código fuente y del control de versiones.

# .env (PHP con phpdotenv, Node.js con dotenv, Python con python-dotenv)
CRAFY_PUBLIC_KEY=pk_xxxxxxxxxxxxxxxx
CRAFY_SECRET_KEY=sk_xxxxxxxxxxxxxxxx
CRAFY_SIGNING_PUBLIC_KEY=base64_xxxxxxxx

Nota: el Public Token no se almacena en el .env porque se obtiene dinámicamente desde el SDK Backend mediante el método getPublicToken().

Luego, en tu código, carga las variables desde el archivo .env:

// PHP (con phpdotenv)
$crafy = new CrafyCAPTCHA(
    $_ENV['CRAFY_PUBLIC_KEY'],
    $_ENV['CRAFY_SECRET_KEY']
);
// Node.js (con dotenv)
require('dotenv').config();
const captcha = new CrafyCAPTCHA(
    process.env.CRAFY_PUBLIC_KEY,
    process.env.CRAFY_SECRET_KEY
);
# Python (con python-dotenv)
import os
from dotenv import load_dotenv
from crafy_captcha import CrafyCAPTCHA

load_dotenv()
captcha = CrafyCAPTCHA(
    os.environ['CRAFY_PUBLIC_KEY'],
    os.environ['CRAFY_SECRET_KEY']
)
⚠️ No subas tu .env al repositorio

Añade .env a tu archivo .gitignore para evitar exponer tus credenciales en sistemas de control de versiones. Incluye un archivo .env.example con las claves vacías como referencia para otros desarrolladores.

El Flujo de Integración

Para proteger un formulario (ej. un Login), el flujo requiere la participación coordinada de tu servidor y del navegador del usuario.

  1. Crear el Flujo (Backend): Usando el SDK de Backend en tu servidor, generas el encryptedIframeOptions (que contiene el Nonce y la configuración del desafío firmados criptográficamente) a través del método createFlow.
  2. Renderizado (Frontend): Pasas el encryptedIframeOptions generado por el servidor al SDK Client-Side (ej. JavaScript o React). Este SDK inyecta de forma segura el iframe de CrafyCAPTCHA, donde el usuario resuelve el reto (ya sea un puzzle visual o una validación transparente e invisible).
  3. Aprobación y Envío (Frontend): Una vez completado el reto con éxito, el Iframe emite un Token Cifrado vía postMessage al SDK Client-Side. Este token se coloca automáticamente en un input oculto y se envía al servidor cuando el usuario hace submit del formulario.
  4. Verificación Final (Backend): Al recibir los datos del formulario web, tu servidor toma el Token y lo valida de forma local, atómica y segura utilizando el método verifyFlow del SDK Backend. Si el resultado es válido (true), el acceso es concedido.

Frontend SDKs

Integra CrafyCAPTCHA en el frontend utilizando nuestras librerías oficiales. Estas se encargan de inyectar el iframe, aplicar estilos y comunicarse de forma asíncrona segura.

SDKs Disponibles del lado del cliente

1. JavaScript Vanilla

Puedes usar el CDN de jsDelivr para incluir la versión Vanilla JS:
GitHub: crafy-captcha-js
Stats y versiones: jsDelivr Package Paquete: npmjs Package

A. Inclusión de la Librería

<script src="https://captcha.crafy.net/cdn-js/1.1.7.js" integrity="sha384-ij1DhfbKim6bT2zXcO46f/BJU4ucV6v0ajMv9NqZ/j2qi99huo54M8A4EhChcxnP" crossorigin="anonymous"></script>

Puedes forzar a la última versión usando https://captcha.crafy.net/cdn-js/last.js (no recomendado para producción).

B. Inicializar el Widget

Coloca un contenedor <div> en tu HTML (ej. <div id="crafy-container"></div>) y llama a CrafyCAPTCHA.init():

document.addEventListener('DOMContentLoaded', () => {
    CrafyCAPTCHA.init(
        'crafy-container',           // ID del div contenedor
        'TU_PUBLIC_KEY',             // Public Key (Credencial)
        'TU_PUBLIC_TOKEN',           // Public Token (Credencial)
        'TU_SIGNING_PUBLIC_KEY',     // Signing Public Key (Base64)
        {
            // Opciones de configuración de seguridad (es obligatorio usar UNA de estas dos)
            optionsUrl: '/crafy-options.php', // (RECOMENDADO: obtiene las opciones mediante Fetch para evitar problemas con caché)
            // encryptedOptions: 'TU_ENCRYPTED_IFRAME_OPTIONS', // (Alternativa: opciones inyectadas directamente por PHP en el HTML)

            // Opciones de apariencia y eventos (estas no se cifran)
            iframeUrl: 'https://captcha.crafy.net/challenge', // URL base del challenge
            inputName: 'CrafyCAPTCHA_token',                 // Nombre del input hidden final
            theme: 'dark',                                   // 'light' o 'dark'
            onSuccess: (token) => {
                console.log("Humano verificado. Token:", token);
            }
        }
    );
});

C. Carga Diferida (defer / async)

Para optimizar el rendimiento de tu página, puedes cargar la librería de forma diferida usando el atributo defer o async en el tag <script>. Esto evita que el script bloquee el renderizado de la página.

<script src="https://captcha.crafy.net/cdn-js/1.1.7.js" integrity="sha384-ij1DhfbKim6bT2zXcO46f/BJU4ucV6v0ajMv9NqZ/j2qi99huo54M8A4EhChcxnP" crossorigin="anonymous" defer></script>

Al usar defer o async, la clase CrafyCAPTCHA podría no estar disponible todavía cuando se ejecuta DOMContentLoaded. Para estos casos, la librería dispara automáticamente un evento personalizado CrafyCAPTCHALoaded en window cuando ha terminado de cargar. Usa este evento para inicializar el widget de forma segura:

<!-- 1. Registrar el listener ANTES de cargar el script -->
<script>
window.addEventListener('CrafyCAPTCHALoaded', () => {
    CrafyCAPTCHA.init(
        'crafy-container',
        'TU_PUBLIC_KEY',
        'TU_PUBLIC_TOKEN',
        'TU_SIGNING_PUBLIC_KEY',
        {
            optionsUrl: '/crafy-options.php',
            inputName: 'CrafyCAPTCHA_token',
            onSuccess: (token) => {
                console.log("Humano verificado. Token:", token);
            }
        }
    );
});
</script>

<!-- 2. Cargar la librería con defer -->
<script src="https://captcha.crafy.net/cdn-js/1.1.7.js" integrity="sha384-ij1DhfbKim6bT2zXcO46f/BJU4ucV6v0ajMv9NqZ/j2qi99huo54M8A4EhChcxnP" crossorigin="anonymous" defer></script>
💡 ¿Cuándo usar cada atributo?
  • defer: El script se descarga en paralelo y se ejecuta después de que el DOM esté listo, respetando el orden de los scripts. Es la opción recomendada.
  • async: El script se descarga en paralelo y se ejecuta tan pronto como se descarga, sin esperar al DOM ni a otros scripts. Útil si no hay dependencias de orden, pero requiere usar el evento CrafyCAPTCHALoaded sí o sí.

En ambos casos, registrar el listener de CrafyCAPTCHALoaded antes del tag <script> que carga la librería garantiza que nunca perderás el evento.

D. Opciones de Inicialización

El quinto parámetro del método init() es un objeto de configuración para personalizar la carga de seguridad, el comportamiento y la apariencia del widget en el lado del cliente:

Propiedad Tipo Descripción
optionsUrl String (Recomendado) Ruta del endpoint en tu servidor que devuelve dinámicamente las opciones. El SDK JavaScript hará una petición POST interna para obtener los datos. Tu backend debe procesar la solicitud llamando al método createFlow() del SDK y retornar un JSON con la propiedad eo: echo json_encode(['eo' => $crafy->createFlow()]);. Ideal si tu web utiliza sistemas de caché (como Cloudflare o WP Rocket) ya que evita conflictos de nonces.
encryptedOptions String (Alternativa Legacy) El string encriptado que genera el servidor usando createFlow(), inyectado directamente por PHP (o NodeJS) en el código HTML de la página. Nota: Si la página es cacheada, el captcha fallará por Replay Attack. Es obligatorio usar optionsUrl o encryptedOptions.
theme String Tema predefinido del widget. Puede ser 'light' o 'dark'. Si no se especifica, se aplicará 'light'.
inputName String Nombre del campo <input type="hidden"> que el widget creará e inyectará en el formulario automáticamente cuando el reto se resuelva. Por defecto es 'CrafyCAPTCHA_token'.
onSuccess Function Función callback que se ejecuta tras validar exitosamente el captcha. Recibe como argumento el token final (en formato base64) que debe enviarse al backend.
iframeUrl String (Opcional) Permite sobrescribir la ruta base donde está montado el endpoint de desafíos.
style Object Objeto para una personalización total de los colores del widget. Las propiedades soportadas son:
  • background: Color de fondo (ej. '#ffffff').
  • backgroundHover: Color de fondo al pasar el cursor.
  • color: Color del texto principal.
  • borderColor: Color del borde del contenedor.
  • primary: Color primario (usado para botones, checks o links).
  • footerColor: Color del texto del pie de página.
Nota: Estas configuraciones de estilo no solo aplican al cuadro de bienvenida, sino que se transmiten al iframe interior para mantener una coherencia visual en todos los puzzles y en los diferentes estados del widget.

E. Control de Carga Automática (Opcional)

Por defecto, el widget intentará precargar el desafío automáticamente en segundo plano después de su inicialización para garantizar que la verificación sea lo más rápida posible para el cliente.

Puedes desactivar este comportamiento usando el método setAutoLoad(false) antes de llamar a init(). Si la carga automática se desactiva, el iframe del desafío no se cargará hasta que el usuario haga clic explícitamente en el checkbox de verificación.

document.addEventListener('DOMContentLoaded', () => {
    // 1. Desactivar carga automática
    CrafyCAPTCHA.setAutoLoad(false);

    // 2. Inicializar
    CrafyCAPTCHA.init('crafy-container', 'TU_PUBLIC_KEY', 'TU_PUBLIC_TOKEN', 'TU_SIGNING_PUBLIC_KEY', {
        optionsUrl: '/crafy-options.php'
    });
});

Para forzar la carga de forma manual o programática desde fuera del script (por ejemplo, cuando el usuario hace focus en un input de texto del formulario), puedes utilizar:

CrafyCAPTCHA.loadIframe();
💡 Estrategia de carga y consumo de cuota
  • setAutoLoad(true) (Por defecto): Mejora notablemente la experiencia del cliente obteniendo una verificación ligeramente más rápida. Sin embargo, cada carga consume cuota de la credencial (descuenta una llamada a challenge).
  • setAutoLoad(false): Ahorra llamadas y consumo de la cuota de la credencial. Te recomendamos encarecidamente ponerlo en false cuando insertas el widget en una página pública donde muchos usuarios entran pero pocos envían el formulario, como por ejemplo un formulario de comentarios al final del artículo de un blog.

F. Múltiples Widgets en la misma página

Puedes renderizar múltiples instancias de CrafyCAPTCHA dentro de la misma página web simplemente llamando a CrafyCAPTCHA.init() varias veces. Esto es extremadamente útil si tienes, por ejemplo, un formulario de registro y otro de login visibles al mismo tiempo (ej. en un modal).

⚠️ Requisitos para múltiples instancias

Para evitar colisiones entre widgets, debes asegurarte de cumplir estrictamente dos reglas:

  1. Cada llamada a init() debe apuntar a un ID de contenedor único.
  2. Cada widget debe configurar un inputName único en su objeto de opciones. Si dos widgets comparten el mismo nombre de input, los tokens se sobrescribirán y el SDK arrojará un error crítico en la consola.
// Inicializar Widget 1 (Login)
CrafyCAPTCHA.init('crafy-container-login', 'PK', 'PT', 'SPK', {
    optionsUrl: '/crafy-options.php',
    inputName: 'CrafyCAPTCHA_token_login'
});

// Inicializar Widget 2 (Registro)
CrafyCAPTCHA.init('crafy-container-register', 'PK', 'PT', 'SPK', {
    optionsUrl: '/crafy-options.php',
    inputName: 'CrafyCAPTCHA_token_register'
});
💡 Envíos en Cascada (Cascading Submit)

Si la arquitectura de tu sistema requiere colocar dos o más widgets distintos dentro del mismo formulario HTML, el SDK maneja la validación global de forma inteligente mediante "Cascading Submit".

Cuando el usuario presiona el botón de "Enviar" del formulario, el SDK detectará automáticamente todos los widgets no resueltos, e irá desplegando el desafío correspondiente de uno en uno, pasándose el turno dinámicamente. El formulario solo se enviará de forma efectiva al backend cuando el usuario haya resuelto satisfactoriamente la totalidad de los widgets anclados a dicho formulario.

2. React SDK

Si usas React, disponemos de un componente oficial que simplifica el flujo de datos.

NPM: @crafyholding/crafy-captcha-react

A. Instalación

npm install @crafyholding/crafy-captcha-react

B. Uso del Componente

import React, { useState } from 'react';
import CrafyCaptcha from '@crafyholding/crafy-captcha-react';

function FormularioLogin() {
    const [captchaToken, setCaptchaToken] = useState(null);

    const manejarEnvio = (e) => {
        e.preventDefault();
        if (!captchaToken) {
            alert('Por favor, resuelve el CAPTCHA primero.');
            return;
        }
        console.log("Enviando al backend...", captchaToken);
    };

    return (
        <form onSubmit={manejarEnvio}>
            <input type="email" placeholder="Tu correo" />
            
            <CrafyCaptcha 
                publicKey="TU_PUBLIC_KEY"
                publicToken="TU_PUBLIC_TOKEN"
                signingPublicKey="TU_SIGNING_PUBLIC_KEY"
                encryptedIframeOptions="TU_ENCRYPTED_IFRAME_OPTIONS"
                options={{ theme: 'dark' }}
                onSuccess={(token) => {
                    console.log("¡CAPTCHA Resuelto!");
                    setCaptchaToken(token);
                }}
            />

            <button type="submit">Entrar</button>
        </form>
    );
}

export default FormularioLogin;
⚠️ Importante

Asegúrate de que el dominio donde usas estos scripts esté en tu lista de orígenes (Referers) autorizados, de lo contrario la API rechazará la petición.

Backend (SDKs & API)

El backend te permite crear flujos de protección y validar los payloads entrantes del cliente. Puedes interactuar con CrafyCAPTCHA a través de la API REST directamente, o usando uno de nuestros SDKs oficiales para simplificar la integración (validación local criptográfica).

SDKs Disponibles del lado del servidor

Métodos principales de los SDKs

Todos los SDKs del lado del servidor implementan tres métodos fundamentales:

getPublicToken()

Obtiene dinámicamente el Public Token actualizado de tu credencial. Este valor cambia cuando tu plan se actualiza (upgrade o downgrade), por lo que no debe hardcodearse. El Public Token obtenido debe pasarse al SDK Client-Side junto con las demás credenciales al inicializar el widget.

createFlow(options)

Genera el encryptedIframeOptions: un string cifrado y firmado criptográficamente que contiene el Nonce del flujo y la configuración del desafío. Este string es el que debes pasar al SDK Client-Side para que inyecte el iframe con el reto correspondiente. Cada llamada a createFlow crea un flujo único y de un solo uso.

verifyFlow(captchaToken)

Valida el captchaToken: el string en formato base64 que el SDK Client-Side genera automáticamente cuando el usuario completa el reto del iframe. Este token viaja al servidor como parte del envío del formulario (por defecto en el campo CrafyCAPTCHA_token). El método verifyFlow verifica la firma HMAC, comprueba la expiración y consume el Nonce de forma atómica (Anti-Replay). Retorna un valor booleano: true si el token es válido, false en caso contrario.

💡 Convención de nombres

Los SDKs de PHP y Node.js utilizan camelCase (createFlow, verifyFlow). El SDK de Python sigue la convención del lenguaje y utiliza snake_case (create_flow, verify_flow). La funcionalidad es idéntica en todos los casos.

Opciones de `createFlow`

El método createFlow acepta un objeto/array de opciones para personalizar el comportamiento del widget de validación. Las principales propiedades son:

Opción Tipo Por Defecto Descripción
mode String 'auto' Define el comportamiento de visualización del puzzle:
  • 'auto': Selecciona automáticamente entre no mostrar (hidden) y mostrar un puzzle visual (puzzle) según el Risk Score del usuario.
  • 'hidden': Fuerza a no mostrar un puzzle visual, validando de fondo.
  • 'puzzle': Fuerza a siempre mostrar un puzzle visual.
puzzles Array [] Establece el orden de los puzzles visuales. Si está vacío, equivale a usar: ['checkbox', 'slider', 'connect']. Mientras más alto sea el Risk Score detectado, el usuario pasará al siguiente puzzle de la lista. También sirve para forzar un único tipo de puzzle visual si se establece un solo nombre de puzzle en el array (ej. ['slider']).

1. PHP SDK

El SDK PHP facilita la creación del flow y la validación atómica. Recomendable para evitar ataques de repetición (Replay Attacks).

GitHub: crafy-captcha-php

A. Instalación

composer require crafycaptcha/crafy-captcha

B. Inicialización y Crear Flow

require __DIR__ . '/vendor/autoload.php';
use Crafy\Captcha\CrafyCAPTCHA;

// Inicializar el SDK
$crafy = new CrafyCAPTCHA(
  'TU_PUBLIC_KEY',
  'TU_SECRET_KEY',
  "https://captcha.crafy.net/api" // Opcional, URL de la API
);

// Generar las opciones seguras para el iframe
$encryptedIframeOptions = $crafy->createFlow([
    'mode' => 'puzzle', // auto | hidden | puzzle
    'puzzles' => [],    // [] | ['checkbox', 'slider', 'connect'] (orden personalizado de menor a mayor riesgo)
]);

// Obtener el Public Token dinámico para pasarlo al SDK Client-Side
$publicToken = $crafy->getPublicToken();

// $encryptedIframeOptions y $publicToken se pasan al inicializador de JS en el frontend

C. Validar el captchaToken (Al procesar el formulario)

$captchaToken = $_POST['CrafyCAPTCHA_token']; // El captchaToken enviado por el frontend

try {
    // verifyFlow valida la firma HMAC, expiración y consume el Nonce (Anti-Replay)
    $isValid = $crafy->verifyFlow($captchaToken);
    
    if($isValid) {
        // CAPTCHA Válido. Procesar login o registro...
        echo "Usuario seguro.";
    } else {
        die("Error de validación del captcha.");
    }
} catch (Exception $e) {
    die("Token inválido o expirado: " . $e->getMessage());
}

D. Almacenamiento Personalizado (Caché & Nonces)

Por defecto, el SDK utiliza almacenamiento atómico en archivos temporales del sistema operativo, garantizando 100% de compatibilidad hacia atrás. Sin embargo, para entornos descentralizados (ej. múltiples servidores en clúster) donde la carpeta /tmp no es compartida, puedes inyectar un motor de almacenamiento diferente.

Opción 1: Base de Datos (PDO)

Ideal si ya tienes una conexión a base de datos y no quieres guardar archivos. Primero, debes ejecutar un script de configuración por única vez para crear la tabla necesaria:

⚠️ Importante

El método installSchema() se debe usar una única vez para configurar la tabla inicial. No debes llamarlo cada vez que instancies CrafyCAPTCHA para crear o verificar un flow.

<?php
// Archivo: setup_database.php

require __DIR__ . '/vendor/autoload.php';

use Crafy\Captcha\PDOStorage;

// Configuración de tu conexión PDO (ejemplo con MySQL/MariaDB)
$dsn = 'mysql:host=127.0.0.1;dbname=mi_app_db;charset=utf8mb4';
$user = 'root';
$password = 'tu_contraseña';

try {
    $pdo = new PDO($dsn, $user, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);

    // Instanciamos el almacenamiento
    $storage = new PDOStorage($pdo);
    
    // Ejecutamos la creación del esquema por única vez
    $storage->installSchema();

    echo "✅ Tabla 'crafy_storage' y sus índices creados correctamente.\n";

} catch (PDOException $e) {
    echo "❌ Error de base de datos: " . $e->getMessage() . "\n";
} catch (Exception $e) {
    echo "❌ Error general: " . $e->getMessage() . "\n";
}

Una vez creada la tabla, puedes inyectar el motor PDO en tu código de forma regular:

use Crafy\Captcha\PDOStorage;

$pdo = new PDO('mysql:host=localhost;dbname=mi_app', 'root', 'password');

// Inyectamos el motor PDO
$crafy->setStorage(new PDOStorage($pdo));

// El uso sigue siendo exactamente el mismo
$encryptedIframeOptions = $crafy->createFlow();
Opción 2: Sistema Propio (Ej. Redis / Memcached)

Puedes crear tu propia clase implementando la interfaz StorageInterface:

use Crafy\Captcha\StorageInterface;

class MiRedisStorage implements StorageInterface {
    private $redis;
    
    public function __construct($redisInstance) {
        $this->redis = $redisInstance;
    }
    
    public function getCache(string $key): ?string {
        return $this->redis->get($key) ?: null;
    }
    
    public function consumeNonce(string $nonce): bool {
        // En Redis, un "DEL" devuelve 1 si existía, lo cual es atómico y perfecto
        return $this->redis->del('nonce_' . $nonce) > 0;
    }
    
    // ... Implementar los demás métodos requeridos de StorageInterface ...
}

$crafy->setStorage(new MiRedisStorage($miConexionRedis));

2. Node.js SDK

El SDK oficial para Node.js permite integrar CrafyCAPTCHA fácilmente en aplicaciones Express, NestJS, etc.

NPM: crafy-captcha
GitHub: crafy-captcha-node

A. Instalación

npm install crafy-captcha

B. Inicialización y Crear Flow

const {CrafyCAPTCHA} = require('crafy-captcha');

async function test() {
    // Inicializar el SDK
    const captcha = new CrafyCAPTCHA('pk_your_public', 'sk_your_secret');
    
    // Crear Flow y generar opciones cifradas
    const encryptedIframeOptions = await captcha.createFlow({
        mode: 'puzzle', // auto | hidden | puzzle
        puzzles: []     // [] | ['checkbox', 'slider', 'connect'] (orden personalizado)
    });

    // Obtener el Public Token dinámico para pasarlo al SDK Client-Side
    const publicToken = await captcha.getPublicToken();

    console.log("Opciones para el iframe:", encryptedIframeOptions);
    console.log("Public Token:", publicToken);
}
test();

C. Validar el captchaToken

const {CrafyCAPTCHA} = require('crafy-captcha');
const captcha = new CrafyCAPTCHA('pk_your_public', 'sk_your_secret');

// Ejemplo en una ruta Express
app.post('/login', async (req, res) => {
    const captchaToken = req.body.CrafyCAPTCHA_token;

    try {
        const isValid = await captcha.verifyFlow(captchaToken);
        
        if(isValid) {
            res.send("Usuario seguro.");
        } else {
            res.status(403).send("Error de validación del captcha.");
        }
    } catch (error) {
        res.status(500).send("Token inválido o expirado.");
    }
});

D. Almacenamiento Personalizado con Adaptadores

Dado que JavaScript es asíncrono por naturaleza, el patrón es muy limpio. Para entornos distribuidos, puedes guardar la caché y los nonces en Redis inyectando un adaptador que herede de StorageAdapter.

const { CrafyCAPTCHA, StorageAdapter } = require('crafy-captcha');
const { createClient } = require('redis');

// 1. Creas tu propio adaptador
class RedisStorage extends StorageAdapter {
  constructor(redisClient) {
    super();
    this.redis = redisClient;
  }

  async getCache(key) {
    return await this.redis.get(key);
  }

  async setCache(key, data, expiresAt) {
    // Calculamos los segundos restantes para que Redis lo auto-borre
    const ttlSeconds = Math.max(0, expiresAt - Math.floor(Date.now() / 1000));
    await this.redis.set(key, data, { EX: ttlSeconds });
  }

  async deleteCache(key) {
    await this.redis.del(key);
  }

  async storeNonce(nonce, expiresAt) {
    // Guardamos con TTL para Garbage Collection automático
    const ttlSeconds = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
    await this.redis.set(`nonce_${nonce}`, '1', { EX: ttlSeconds });
  }

  async consumeNonce(nonce) {
    // En Redis, DEL devuelve 1 si borró algo y 0 si no existía. Es atómico (Anti-Replay Attack)
    const deletedCount = await this.redis.del(`nonce_${nonce}`);
    return deletedCount > 0;
  }

  async clearAllNonces() {
    const keys = await this.redis.keys('nonce_*');
    if (keys.length > 0) await this.redis.del(keys);
    return keys.length;
  }

  async gcNonces() {
    // No necesitamos Garbage Collection en Redis porque le pusimos { EX: ttl }
    return;
  }
}

// 2. Uso en la aplicación
async function iniciarApp() {
  const redisClient = createClient({ url: 'redis://localhost:6379' });
  await redisClient.connect();

  const captcha = new CrafyCAPTCHA('pk_...', 'sk_...');
  
  // 3. Inyectamos tu nuevo motor de almacenamiento
  captcha.setStorage(new RedisStorage(redisClient));

  // 4. Todo funciona mágicamente sobre Redis
  const flowOptions = await captcha.createFlow({ mode: 'invisible' });
}
iniciarApp();

3. Python SDK

El SDK oficial para Python permite integrar CrafyCAPTCHA en aplicaciones Django, Flask, FastAPI y cualquier otro framework de Python.

🐍 Convención snake_case

El SDK de Python utiliza snake_case siguiendo las convenciones del lenguaje: create_flow() en lugar de createFlow() y verify_flow() en lugar de verifyFlow().

PyPI: crafy-captcha
GitHub: crafy-captcha-python

A. Instalación

pip install crafy-captcha

B. Inicialización y Crear Flow

from crafy_captcha import CrafyCAPTCHA

# Inicializar el SDK
captcha = CrafyCAPTCHA('TU_PUBLIC_KEY', 'TU_SECRET_KEY')

# Generar las opciones seguras para el iframe
encrypted_iframe_options = captcha.create_flow({
    'mode': 'puzzle',  # auto | hidden | puzzle
    'puzzles': []      # [] | ['checkbox', 'slider', 'connect']
})

# Obtener el Public Token dinámico para pasarlo al SDK Client-Side
public_token = captcha.get_public_token()

# encrypted_iframe_options y public_token se pasan al frontend
print(encrypted_iframe_options)
print(public_token)

C. Validar el captchaToken

from crafy_captcha import CrafyCAPTCHA

captcha = CrafyCAPTCHA('TU_PUBLIC_KEY', 'TU_SECRET_KEY')

# El captchaToken enviado por el frontend (ej. desde un formulario Flask/Django)
captcha_token = request.form.get('CrafyCAPTCHA_token')

try:
    is_valid = captcha.verify_flow(captcha_token)
    
    if is_valid:
        print("Usuario seguro.")
    else:
        # Obtener el motivo exacto del fallo
        motivo = captcha.get_last_flow_verify_error()
        print(f"Error de validación: {motivo}")
except Exception as e:
    print(f"Token inválido o expirado: {e}")

D. Almacenamiento Personalizado con Adaptadores

Si tu aplicación se ejecuta en múltiples contenedores (ej. Kubernetes o Docker), puedes usar Redis para almacenar los datos temporalmente heredando de StorageAdapter.

import redis
import time
from crafy_captcha import CrafyCAPTCHA, StorageAdapter

# 1. Creas tu propio adaptador
class RedisStorage(StorageAdapter):
    def __init__(self, redis_client):
        self.redis = redis_client

    def get_cache(self, key: str) -> str:
        data = self.redis.get(key)
        return data.decode('utf-8') if data else None

    def set_cache(self, key: str, data: str, expires_at: int) -> None:
        ttl = max(0, expires_at - int(time.time()))
        self.redis.setex(key, ttl, data)

    def delete_cache(self, key: str) -> None:
        self.redis.delete(key)

    def store_nonce(self, nonce: str, expires_at: int) -> None:
        ttl = max(0, expires_at - int(time.time()))
        self.redis.setex(f"nonce_{nonce}", ttl, "1")

    def consume_nonce(self, nonce: str) -> bool:
        # Atómico: delete devuelve > 0 si existía y 0 si ya fue consumido
        deleted_count = self.redis.delete(f"nonce_{nonce}")
        return deleted_count > 0

    def clear_all_nonces(self) -> int:
        keys = self.redis.keys("nonce_*")
        if keys:
            self.redis.delete(*keys)
        return len(keys)

    def gc_nonces(self) -> None:
        pass # En Redis no necesitamos Garbage Collection manual (se borran solos)

# === USO EN LA APLICACIÓN ===

# 1. Conectarse a Redis
r = redis.Redis(host='localhost', port=6379, db=0)

# 2. Inicializar el SDK
captcha = CrafyCAPTCHA('pk_...', 'sk_...')

# 3. Inyectar el motor de almacenamiento
captcha.set_storage(RedisStorage(r))

# 4. El SDK ahora funcionará 100% sobre Redis de manera transparente
public_token = captcha.get_public_token()

Plugin para WordPress

Si utilizas WordPress, disponemos de un plugin oficial que facilita enormemente la integración de CrafyCAPTCHA en los formularios nativos sin necesidad de tocar código.

Instalación paso a paso

  1. Ir a Plugins: En el panel de administración de tu WordPress, dirígete a Plugins > Añadir nuevo plugin.
  2. Buscar el Plugin: En la barra de búsqueda superior, escribe CrafyCAPTCHA (puedes ver la página oficial del plugin aquí).
  3. Instalar y Activar: Haz clic en el botón Instalar ahora y, al finalizar la instalación, pulsa en Activar.
  4. Configurar: En el menú lateral izquierdo de tu WordPress, dirígete a Ajustes > CrafyCAPTCHA. Allí podrás ingresar tus credenciales (Public Key, Secret Key y Signing Public Key) que puedes obtener desde tu Panel de Control.
💡 Protección automática nativa

Una vez activado y configurado, el plugin inyectará y validará automáticamente el CAPTCHA en los formularios estándar de WordPress, como la página de inicio de sesión (wp-login.php), el formulario de registro y el de comentarios.

Ejemplos de Integración

Para facilitar la implementación de CrafyCAPTCHA, ofrecemos ejemplos listos para usar que puedes tomar como referencia o base para tus proyectos.

Ejemplo Completo: Frontend (JS) + Backend (PHP)

Este ejemplo demuestra paso a paso cómo integrar el widget en un formulario HTML utilizando nuestro SDK de JavaScript, y cómo recibir y validar atómicamente el token resultante en tu servidor usando nuestro SDK de PHP.

Tipos de Puzzles

Dependiendo del Risk Score detectado, el sistema puede presentar diferentes niveles de fricción. Puedes probar cada uno de los puzzles forzando el tipo de reto en nuestras demos:

Integración con Cloudflare Turnstile

De forma predeterminada, CrafyCAPTCHA cuenta con su propio motor de seguridad y algoritmos criptográficos (Proof-of-Work). Sin embargo, puedes potenciar esta seguridad integrando Cloudflare Turnstile de forma nativa. Esta capa adicional es opcional y se configura directamente en tu Panel de Control.

💡 Beneficios de Turnstile

Al integrar Turnstile, sumas la telemetría global y el análisis de amenazas de Cloudflare al motor de riesgo de CrafyCAPTCHA. El sistema creará y gestionará desafíos Turnstile de forma automática, brindando una experiencia invisible y respetuosa de la privacidad para los usuarios genuinos, al tiempo que detiene tráfico automatizado sofisticado.

Pasos de Sincronización

No necesitas modificar tu código; todo se gestiona desde el panel:

  1. Crea una cuenta en Cloudflare o inicia sesión.
  2. Accede a tu panel de Tokens de API.
  3. Haz clic en el botón Crear token y selecciona Comenzar en "Crear token personalizado".
  4. Asígnale un nombre, por ejemplo: CrafyCAPTCHA Token.
  5. En Permisos, añade estos dos permisos exactos:
    • CuentaTurnstileEditar
    • CuentaConfiguración de la cuentaLeer
  6. Haz clic en Ir al resumen y luego en Crear token.
  7. Copia el Token de API generado.
  8. En tu Panel de Control de CrafyCAPTCHA, ve al menú de usuario, selecciona Sincronizar Cloudflare, y pega tu token.

Una vez sincronizado, CrafyCAPTCHA creará automáticamente los widgets de Turnstile necesarios en tu cuenta de Cloudflare para cada uno de tus dominios, orquestando las validaciones transparentes sin que tengas que intervenir.

Autenticación (API REST)

Si deseas consumir el API de administración o endpoints protegidos, primero debes obtener un JWT (JSON Web Token) autenticándote con tus credenciales.

Obtener Token

Endpoint: POST /api/?action=authenticate

fetch('/api/?action=authenticate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        public_key: 'TU_PUBLIC_KEY',
        secret_key: 'TU_SECRET_KEY'
    })
})
.then(res => res.json())
.then(data => {
    if(data.status === 'success') {
        console.log("Bearer Token:", data.data.token);
        console.log("Public Token actualizado:", data.data.public_token);
        console.log("Expira en (segundos):", data.data.expires_in);
    }
});

La respuesta exitosa incluye:

Campo Tipo Descripción
token String El JWT Bearer Token para autenticar llamadas posteriores a la API.
public_token String El Public Token actualizado de tu credencial. Este es el mismo valor que retorna getPublicToken() en los SDKs.
expires_in Integer Tiempo de vida del Bearer Token en segundos.

Uso del Token

Para todos los demás endpoints, debes pasar el token en el Header Authorization:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsIn...

Endpoints del API

⚠️ Importante sobre la creación de Flujos

La lógica criptográfica para generar el encryptedIframeOptions (el método createFlow) reside de forma nativa dentro de los SDKs Backend y no está disponible como endpoint en la API REST por motivos de arquitectura descentralizada (Zero-RTT) y seguridad. Por lo tanto, se recomienda encarecidamente utilizar uno de los SDKs oficiales, o en su defecto, hacer un port de la lógica de encriptación a tu lenguaje de programación.

Check Flow (Validación Alternativa)

Si no puedes usar un SDK, puedes validar el token haciendo una llamada HTTP a este endpoint atómico.

Endpoint: POST /api/?action=check_flow
Autenticación: Requiere JWT Bearer

// Payload a enviar
{
    "flow_token": "uuid-del-token-generado-por-el-frontend"
}

// Respuesta Exitosa
{
    "status": "success",
    "data": {
        "valid": true,
        "risk_score": 0.1,
        "message": "Flow consumido atómicamente"
    }
}

Nota: Este endpoint consume el token asegurando que el estado cambie a consumed. Intentar validarlo dos veces arrojará error (previene Replay Attacks).

Rate Limits y Facturación

Para proteger nuestra infraestructura y asegurar disponibilidad para todos, CrafyCAPTCHA utiliza un modelo estricto de control de tráfico en frontera.

1. Rate Limit en Autenticación

El endpoint de Login (/api/?action=authenticate) está protegido para evitar fuerza bruta en tus credenciales. Límite: 10 intentos por minuto por IP, y 2 por segundo. Exceder esto devolverá HTTP 429.

2. Límites por Usuario y Plan

El uso general del API está limitado según la cuota de tu plan de facturación activo. El plan viene incrustado en el Bearer Token (JWT) una vez que te autenticas.

Plan Peticiones por Minuto Peticiones por Segundo (Ráfaga)
Starter 8 req / min 2 req / sec
Growth 150 req / min 20 req / sec
Scale 500 req / min 50 req / sec

Ver precios

💡 Actualización de Plan

Al cambiar de plan (upgrade o downgrade), ten en cuenta lo siguiente:

  • Public Key, Secret Key y Signing Public Key nunca cambian.
  • El Public Token se regenera automáticamente. Los SDKs Backend lo gestionan de forma dinámica a través del método getPublicToken(). Consulta la sección Credenciales para más detalles.
  • Para que los nuevos Rate Limits surtan efecto en la API REST, deberás volver a autenticarte (/api/?action=authenticate) para obtener un nuevo Bearer Token con tus límites actualizados.

Si superas los límites de tu plan, recibirás una respuesta 429 Too Many Requests. Te sugerimos implementar retrasos exponenciales (Exponential Backoff) en tus scripts ante estos eventos.