Emitir Open Badges desde WordPress (sin un plugin pesado)

Emite Open Badges desde cualquier plugin de curso/LMS de WordPress (LearnDash, LifterLMS, Tutor) llamando a la API de Badges Ninja al completar — sin instalar un plugin de insignias aparte.

Nacho Coll Por Actualizado 14 min de lectura
Emite Open Badges desde cualquier plugin de curso/LMS de WordPress (LearnDash, LifterLMS, Tutor) llamando a la API de Badges Ninja al completar — sin instalar un plugin de insignias aparte.

Si llevas cursos en WordPress, ya conoces el proceso: alguien termina un módulo en LearnDash, LifterLMS o Tutor LMS, el LMS lo marca como “completado” y luego… no pasa nada verificable. Quizás envías un PDF por correo. Quizás instalas un plugin de insignias dedicado para WordPress que añade sus propias tablas de base de datos, su propia biblioteca de imágenes y su propia idea de qué es una “insignia” — una que nadie fuera de tu sitio puede verificar.

Hay un camino más sencillo. Los plugins de cursos de WordPress ya disparan un action hook en el momento en que alguien completa un curso. Puedes capturar ese hook y hacer una sola llamada HTTP a la API de Badges Ninja. Sin plugin adicional, sin hosting adicional, sin formato de insignia propietario — solo una credencial Open Badge v2.0 conforme al estándar que tu destinatario puede verificar desde cualquier sitio, añadir a LinkedIn y descargar como PDF.

Este artículo recorre el patrón una vez, y luego muestra el hook específico para LearnDash, LifterLMS y Tutor LMS.

¿Por qué no usar simplemente un plugin de insignias de WordPress?

Los plugins de insignias dedicados para WordPress resuelven un problema más limitado de lo que parece. Se les da bien mostrar una imagen en una página de “mis logros” dentro de tu sitio. Suelen ser mucho más flojos en las cosas que realmente importan para una credencial:

  • Portabilidad. Una insignia que solo existe como un shortcode en tu sitio de WordPress no es una credencial — es una decoración. En el momento en que el destinatario cambia de trabajo, cambia de bandeja de entrada o tu sitio deja de funcionar, la “prueba” desaparece con él.
  • Verificación. Cualquiera debería poder comprobar que una credencial es real sin iniciar sesión en tu administración de WordPress. Open Badge v2.0 te da una URL de verificación pública y JSON-LD legible por máquina por diseño.
  • Superficie de mantenimiento. Cada plugin de WordPress es una cosa más que parchear, una tabla más de base de datos que respaldar, un punto de fallo más durante una actualización del núcleo.

Llamar a una API externa al completar el curso evita los tres problemas. WordPress sigue siendo responsable de lo que hace bien — entrega de contenido y seguimiento del progreso — y la credencial en sí vive en un sistema construido específicamente para emitirla, verificarla y compartirla.

El patrón: un hook, una llamada HTTP

Todos los plugins de LMS de WordPress importantes disparan una acción cuando un usuario completa un curso. La integración es siempre los mismos tres pasos:

  1. Engánchate a la acción de finalización de tu LMS específico.
  2. Busca (o crea) la identidad del destinatario — normalmente solo su correo de la cuenta de WordPress y su nombre para mostrar.
  3. Llama a POST /awards en la API de Badges Ninja con wp_remote_post(), pasando el ID de la insignia y los datos del destinatario.

Primero necesitarás una clave de API. Genérala desde tu panel, en API Keys — consulta la guía de claves de API para el recorrido completo. Las claves se muestran una sola vez al crearlas, así que guárdala en wp-config.php como una constante en lugar de en la base de datos:

define( 'BADGES_NINJA_API_KEY', 'bws_your_32_hex_key_here' );
define( 'BADGES_NINJA_BADGE_ID', 'your-badge-id' );

Newly created key revealed once

Mantén esta clave en el servidor. Todo en esta guía se ejecuta en PHP en tu servidor de WordPress — wp_remote_post() es una llamada del lado del servidor, y wp-config.php nunca se envía al navegador. Eso es exactamente lo que quieres: la clave viaja de servidor a servidor por HTTPS, y ningún visitante del sitio puede leerla. Nunca muevas esta llamada a JavaScript del lado del cliente — un fetch() desde el navegador con la clave incrustada la expondría a cualquiera a través de las herramientas de desarrollador de su navegador. Dos hábitos que merece la pena mantener: asegúrate de que tu hosting sirva wp-config.php como PHP ejecutado (nunca como texto plano, y no dejes una copia wp-config.php.bak en el webroot), y crea una clave de API específica para esta integración para poder revocarla de forma independiente si alguna vez se ve comprometida — sin romper tus otras herramientas.

Una función auxiliar compartida mantiene cada hook de LMS ligero:

function badges_ninja_issue_award( $recipient_email, $recipient_name, $badge_id ) {
	$response = wp_remote_post( 'https://api.badges.ninja/awards', array(
		'headers' => array(
			'X-Api-Key'    => BADGES_NINJA_API_KEY,
			'Content-Type' => 'application/json',
		),
		'body' => wp_json_encode( array(
			'badgeId'   => $badge_id,
			'recipient' => array(
				'name'  => $recipient_name,  // must be at least 5 characters
				'email' => $recipient_email,
			),
			'issuedOn'  => round( microtime( true ) * 1000 ), // epoch milliseconds
		) ),
		'timeout' => 15,
	) );
	if ( is_wp_error( $response ) ) {
		error_log( 'Badges Ninja award failed: ' . $response->get_error_message() );
		return false;
	}
	$code = wp_remote_retrieve_response_code( $response );
	if ( $code >= 400 ) {
		error_log( 'Badges Ninja award failed with HTTP ' . $code . ': ' . wp_remote_retrieve_body( $response ) );
		return false;
	}
	return json_decode( wp_remote_retrieve_body( $response ), true );
}

El correo del destinatario se codifica con hash SHA-256 en el servidor en cuanto llega a Badges Ninja, y la respuesta incluye la URL de verificación del award — útil si quieres mostrar un enlace de “ver tu credencial” justo ahí en WordPress después de que la llamada tenga éxito.

LearnDash

LearnDash dispara learndash_course_completed con un único argumento de tipo array que contiene user y course:

add_action( 'learndash_course_completed', function( $data ) {
	$user   = $data['user'];
	$course = $data['course'];
	// Map LearnDash course ID → your Badges Ninja badge ID.
	// A simple approach: store the badge ID in a course custom field.
	$badge_id = get_post_meta( $course->ID, 'badges_ninja_badge_id', true );
	if ( ! $badge_id ) {
		return; // no badge configured for this course
	}
	badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
} );

Guardar el ID de la insignia como metadato del curso (en lugar de una única constante global BADGES_NINJA_BADGE_ID) es la decisión correcta en cuanto tienes más de un curso — cada curso se corresponde con su propio diseño de insignia.

LifterLMS

LifterLMS dispara lifterlms_course_completed con $user_id y $course_id:

add_action( 'lifterlms_course_completed', function( $user_id, $course_id ) {
	$user     = get_userdata( $user_id );
	$badge_id = get_post_meta( $course_id, 'badges_ninja_badge_id', true );
	if ( ! $user || ! $badge_id ) {
		return;
	}
	badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
}, 10, 2 );

Tutor LMS

Tutor LMS dispara tutor_course_complete_after con el ID del curso, y tú obtienes el usuario actual desde la sesión:

add_action( 'tutor_course_complete_after', function( $course_id ) {
	$user_id  = get_current_user_id();
	$user     = get_userdata( $user_id );
	$badge_id = get_post_meta( $course_id, 'badges_ninja_badge_id', true );
	if ( ! $user || ! $badge_id ) {
		return;
	}
	badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
} );

Si tu configuración de Tutor LMS dispara la finalización desde un proceso en segundo plano (por ejemplo, mediante una autocorrección de examen), sustituye get_current_user_id() por el ID de usuario que proporcione tu hook específico — revisa el payload con error_log( print_r( func_get_args(), true ) ) una vez durante la configuración para confirmar los argumentos.

Diseñar la insignia antes de conectar el hook

Antes de que nada de esto se dispare, necesitas una insignia que otorgar. El diseñador visual cubre esto sin necesidad de tener un diseñador en el equipo — más de 80 plantillas de formas, colores personalizados, biblioteca de iconos y carga de logotipo, todo con ajuste a la cuadrícula. Si eres nuevo en el diseñador, Diseñando tu primer certificado digital verificable te guía a la hora de elegir una plantilla y lanzar algo que los destinatarios realmente querrán compartir.

Una vez que la insignia existe, coge su ID desde el panel (o mediante GET /badges) y colócalo en el campo personalizado de tu curso.

Lo que realmente recibe el destinatario

Esta es la parte que un plugin de insignias casero para WordPress no puede replicar. Cuando POST /awards tiene éxito, el destinatario recibe:

  • Una URL de verificación única que cualquiera puede abrir — sin necesidad de iniciar sesión en WordPress — que muestra que la credencial es real, quién la emitió y cuándo.
  • Un código QR y un certificado PDF en A4, generados automáticamente.
  • Un botón de Añadir a perfil de LinkedIn, si tu emisor tiene configurado un ID de organización de LinkedIn — consulta Añadir un botón “Añadir a perfil” de LinkedIn para la configuración de una sola vez.
  • Acceso a su propio portal de destinatario en badges.ninja/me, mediante inicio de sesión con enlace mágico — sin contraseña que olvidar, sin cuenta aparte que gestionar en tu sitio de WordPress.
  • Open Badge v2.0 JSON-LD conforme al estándar, para que cualquier verificador compatible con OB (no solo Badges Ninja) pueda validarla.

Nada de eso es tarea de WordPress construirlo, y nada de eso debería vivir solo dentro de tu base de datos de WordPress.

Gestionar los fallos con elegancia

Los hooks de finalización de curso se disparan durante una solicitud de página en directo, y una llamada a la API lenta o fallida no debería bloquear la pantalla de “enhorabuena” del alumno. Dos ajustes prácticos en cuanto avances más allá de una prueba de concepto:

No dejes que un award fallido bloquee la interfaz. La función auxiliar de arriba ya registra el error y devuelve false en lugar de lanzar una excepción — LearnDash, LifterLMS y Tutor continúan con su flujo normal de finalización sin importar lo que devuelva badges_ninja_issue_award().

Ponlo en cola en lugar de llamarlo en línea, a escala. Si estás procesando cohortes grandes a través de un solo curso a la vez (una fecha de inicio de cohorte, un desbloqueo de repetición de webinar), dispara un evento programado de WP-Cron desde el hook en lugar de llamar a la API de forma síncrona:

add_action( 'learndash_course_completed', function( $data ) {
	wp_schedule_single_event( time() + 30, 'badges_ninja_deferred_award', array(
		$data['user']->user_email,
		$data['user']->display_name,
		get_post_meta( $data['course']->ID, 'badges_ninja_badge_id', true ),
	) );
} );
add_action( 'badges_ninja_deferred_award', 'badges_ninja_issue_award', 10, 3 );

Esto reparte la carga y evita que una sola solicitud lenta se convierta en el problema del alumno. Para lotes verdaderamente grandes (una cohorte entera del pasado, migrada de una sola vez), evita por completo la vía del hook de WordPress y usa awards masivos vía CSV — sube el archivo una vez, y la plataforma pagina la emisión del lado del servidor con pausa/reanudación incorporadas.

Una nota sobre la idempotencia

Si tu LMS alguna vez dispara su hook de finalización más de una vez para el mismo usuario (un caso límite habitual de LearnDash con reintentos de examen, o una resincronización de Tutor LMS), no quieres otorgar la misma insignia dos veces. La protección más sencilla es un indicador de user-meta de WordPress que estableces justo después de una llamada correcta:

if ( get_user_meta( $user_id, 'badges_ninja_awarded_' . $course_id, true ) ) {
	return; // already issued
}
$result = badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
if ( $result ) {
	update_user_meta( $user_id, 'badges_ninja_awarded_' . $course_id, true );
}

Si estás emitiendo a volumen real y quieres gestionar esto de forma centralizada en lugar de por sitio, consulta el manejo de eventos mediante webhooks o la guía rápida completa de la API para las formas de solicitud/respuesta de cada endpoint.

Cuando este patrón se queda pequeño para un solo curso

Los ejemplos anteriores asumen una insignia por curso. En cuanto manejas credenciales apilables — una insignia de itinerario que se desbloquea después de varias insignias de curso, o una insignia final que depende de varios prerrequisitos — saca el mapeo de “qué insignia, para qué finalización” del post meta y llévalo a una pequeña tabla de consulta, o dirígelo desde una automatización respaldada por una hoja de cálculo (Airtable o Google Sheets) que dispare la misma llamada a POST /awards. La llamada a la API en sí no cambia; solo cambia lo que decide cuándo hacerla.

Resolución de problemas de la integración

Un puñado de problemas explica la mayor parte de la fricción que la gente encuentra al conectar esto por primera vez:

El hook nunca se dispara. Confirma que te estás enganchando a la acción correcta para la versión real de tu plugin — LearnDash en particular ha cambiado los nombres de sus hooks de finalización a lo largo de versiones mayores (las instalaciones más antiguas a veces todavía dependen de que learndash_course_completed se dispare desde una ruta de código distinta a las más nuevas). Añade un error_log() temporal al principio de tu callback y observa wp-content/debug.log mientras completas un curso de prueba como un usuario desechable.

wp_remote_post() agota el tiempo de espera. Los entornos de hosting compartido a veces bloquean las conexiones HTTPS salientes a hosts desconocidos, o limitan los tiempos de espera de solicitud por debajo de los 15 segundos establecidos arriba. Si las llamadas agotan el tiempo de espera de forma constante, consulta con tu hosting si se permiten conexiones salientes a api.badges.ninja, y confirma que tu extensión curl de PHP tiene certificados CA actualizados — un paquete de certificados obsoleto es un fallo silencioso habitual en pilas de hosting compartido más antiguas.

400 Bad Request. Al payload le falta un campo requerido o tiene la forma incorrecta. POST /awards espera badgeId, un objeto recipient anidado con name y email, y una marca de tiempo issuedOn — el helper de arriba envía exactamente eso. El tropiezo más habitual es el nombre del destinatario: debe tener al menos 5 caracteres, así que un nombre para mostrar de WordPress como “Jo” se rechaza. Recurre al nombre completo de la cuenta (o a la parte local del correo) cuando el nombre para mostrar sea demasiado corto.

401 Unauthorized. La constante de la clave de API no se está cargando, o fue regenerada después de establecer la constante. Las claves se muestran una sola vez al crearse y no se pueden recuperar de nuevo — si has perdido la pista de cuál clave está activa, elimina la obsoleta y emite una nueva en lugar de adivinar.

Insignia emitida, pero con el diseño de insignia equivocado. Esto casi siempre es un valor de post-meta badges_ninja_badge_id obsoleto que quedó de pruebas anteriores. Como el mapeo vive en el post del curso, es fácil olvidar que lo cambiaste después de duplicar un curso para una nueva cohorte — merece la pena revisarlo rápidamente cada vez que clonas un curso.

Awards duplicados para la misma persona. Cubierto arriba con la protección de user-meta, pero merece la pena repetirlo: no está garantizado que los hooks de finalización del LMS se disparen exactamente una vez. Trata “ya otorgado” como un caso normal que hay que comprobar, no como un caso límite que se pueda ignorar.

Preguntas frecuentes

¿Esto funciona con paquetes de cursos de WooCommerce? Sí — si una compra de paquete debería disparar una insignia con independencia de la finalización del curso (por ejemplo, una insignia de “miembro certificado” para cualquiera que compre un paquete concreto), engánchate a woocommerce_order_status_completed en lugar de una acción de finalización de LMS, y busca el ID de la insignia en el meta del producto en lugar del curso.

¿Pueden los destinatarios obtener la insignia sin una cuenta de WordPress? Sí, y esa es precisamente la idea. El award está vinculado a su dirección de correo en Badges Ninja, no a un registro de usuario de WordPress. Si tu sitio permite compra como invitado o finalización anónima de exámenes, solo asegúrate de capturar una dirección de correo real para pasarla a badges_ninja_issue_award().

¿Y si quiero revisar los awards antes de que salgan? Evita disparar la llamada directamente desde el hook de finalización. En su lugar, escribe el award pendiente (usuario, curso, marca de tiempo) en una tabla personalizada o un tipo de post, y emítelo desde una tarea programada o un botón de administración “Aprobar y otorgar” que llame a la misma función auxiliar bajo demanda.

¿Necesito una insignia distinta por curso, o una puede cubrir varios? Ambas opciones funcionan. Algunos emisores crean una insignia por curso para mayor granularidad; otros crean una única insignia de “programa completado” y solo la otorgan una vez que se completa cada curso requerido, comprobando la finalización de prerrequisitos dentro del hook antes de llamar a la API.


¿Listo para emitir tu primera credencial verificable? Empieza gratis en badges.ninja — diseñador visual, página de verificación pública, certificado PDF, salida Open Badge v2.0. No se requiere tarjeta de crédito.

Nacho Coll

Sobre el autor

Founder & Engineer at Badges Ninja

Nacho founded Badges Ninja to make issuing verifiable digital credentials as simple as a single API call — Open Badge v2.0 badges and certificates, minted, hosted, and verifiable without standing up your own issuer infrastructure. Writes about the Open Badges spec, credential verification, and running a credentialing platform serverless on AWS, from the operator side of the wire.

Volver al Blog

Artículos Relacionados