Integra la verificación de insignias en tu sitio web (sin iframe, sin SDK)
Añade un pequeño fragmento de JavaScript (o una llamada desde el servidor) a tu sitio para verificar cualquier Open Badge contra un endpoint JSON-LD público. Sin iframe, sin rastreadores de terceros.
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.
Si emites credenciales digitales, tarde o temprano alguien te preguntará: «¿cómo sé que esta insignia es auténtica?». La respuesta honesta nunca debería ser «confía en el PNG». Open Badge v2.0 lo resuelve con un endpoint de verificación público y legible por máquinas, y poner un widget de «Verificada» en tu propio sitio lleva unas diez líneas de código: sin iframe, sin script de terceros y sin SDK.
Este artículo muestra exactamente cómo llamar al endpoint y mostrar el resultado, con JavaScript puro y en el servidor con Node y Python. También explica qué comprueba realmente la «verificación», dónde encaja el endpoint junto a las páginas para personas que badges.ninja ya ofrece a los destinatarios, y los errores que hacen que un widget de verificación parezca terminado cuando en realidad no demuestra nada.
Qué significa realmente «verificada»
Una aserción de Open Badge v2.0 es un documento JSON-LD con una forma fija: la aserción en sí (quién la recibió y cuándo), una referencia al BadgeClass (qué es y quién define los criterios) y una referencia al perfil del emisor (quién la respalda). Cuando «verificas» una insignia, en realidad compruebas tres cosas distintas:
- Si la aserción existe y se resuelve. El ID del premio es real, no inventado ni manipulado.
- Si ha sido revocada. Un emisor puede revocar una insignia a posteriori —una credencial emitida por error, una infracción de normas, una certificación caducada— y un verificador correcto debe mostrarlo, no limitarse a confirmar que el registro existe.
- Si la cadena es coherente. Las referencias de la aserción al badge y al emisor se resuelven en registros reales y coincidentes, no en IDs huérfanos o que no cuadran.
Un widget de verificación que solo hace ping a una URL y comprueba un estado 200 se salta por completo el punto 2. Una aserción revocada se sigue resolviendo sin problema: simplemente lleva un indicador de revocación en el contenido. Si tu widget solo comprueba que «la petición ha funcionado», una credencial revocada aparecerá como verificada, y eso es peor que no tener widget.
La página pública para personas frente al endpoint de la API
Cada premio emitido en https://badges.ninja tiene una URL pública que los destinatarios pueden compartir directamente, sin necesidad de registrarse para verla:

Esa página es la que la mayoría de destinatarios enlazará desde LinkedIn, un currículum o una firma de correo: está pensada para personas, muestra la imagen de la insignia, el emisor y la fecha de emisión, y no necesita nada de tu parte para funcionar. El endpoint de la API que verás a continuación devuelve la misma aserción como JSON-LD estructurado, que es lo que quieres cuando construyes tu propia interfaz en lugar de limitarte a enlazar a la nuestra.
El endpoint
GET https://api.badges.ninja/certify-badge/award/{guid}
No requiere cabecera de autenticación: es público a propósito, porque todo el sentido de Open Badges es que cualquiera (un empleador, un reclutador, un colegio profesional) pueda verificar una credencial sin tener cuenta. Los detalles completos del contrato, incluidos los nombres exactos de los campos de la aserción devuelta, están en la documentación de verificación.
Pasa el GUID del premio, que tendrás en tus propios registros de emisión o que podrás extraer de un enlace badges.ninja/awards/<guid> que un destinatario haya compartido contigo.
JS puro: obtener y mostrar
Este es el widget completo. Sin paso de compilación, sin paquete npm, lo pegas directamente en cualquier página:
<div id="badge-verify"></div>
<script>
async function renderBadgeVerification(awardId, mountId) {
const el = document.getElementById(mountId);
el.textContent = "Checking badge status";
try {
const res = await fetch(
`https://api.badges.ninja/certify-badge/award/${awardId}`
);
if (!res.ok) {
el.textContent = "Credential not found.";
return;
}
const assertion = await res.json();
// —— revocation check first, a resolvable assertion isn't enough on its own
if (assertion.revoked) {
el.textContent = `Revoked: ${assertion.revocationReason || "no reason given"}`;
return;
}
el.textContent = `Verified, issued ${new Date(assertion.issuedOn).toLocaleDateString()}`;
} catch (err) {
el.textContent = "Verification check failed.";
}
}
renderBadgeVerification("REPLACE_WITH_AWARD_ID", "badge-verify");
</script>
Sustituye REPLACE_WITH_AWARD_ID por un GUID de premio real. Esa es toda la integración en el cliente: sin frontera de iframe con la que pelearte en el CSS y sin un script de terceros que traiga sus propias analíticas o píxeles de seguimiento junto a la comprobación de verificación.
En el servidor: Node
Si prefieres verificar en tu backend —por ejemplo, antes de mostrar una insignia de «Socio verificado» en una página de perfil que controlas, o antes de aceptar una credencial como prueba en un flujo de incorporación—, la llamada es idéntica salvo por la parte del DOM:
async function verifyAward(awardId) {
const res = await fetch(
`https://api.badges.ninja/certify-badge/award/${awardId}`
);
if (!res.ok) return { verified: false, reason: "not_found" };
const assertion = await res.json();
if (assertion.revoked) {
return { verified: false, reason: assertion.revocationReason || "revoked" };
}
return { verified: true, issuedOn: assertion.issuedOn, badge: assertion.badge };
}
En el servidor: Python
import requests
# —— resolve the assertion, then check revocation before trusting it
def verify_award(award_id):
res = requests.get(f"https://api.badges.ninja/certify-badge/award/{award_id}")
if res.status_code != 200:
return {"verified": False, "reason": "not_found"}
assertion = res.json()
if assertion.get("revoked"):
return {"verified": False, "reason": assertion.get("revocationReason", "revoked")}
return {"verified": True, "issued_on": assertion.get("issuedOn"), "badge": assertion.get("badge")}
Ambos ejemplos tratan una respuesta distinta de 200 como «no encontrada» en lugar de lanzar una excepción. Un ID de premio roto o inventado es una entrada esperable en un endpoint de verificación público, no algo excepcional, así que gestiónalo como una rama normal y no como un error capturado.
Dar estilo al resultado en lugar de incrustar la interfaz de otro
Como el widget anterior es simplemente una llamada fetch hacia tu propio DOM, controlas cada píxel. Un patrón habitual son tres estados visuales asignados directamente desde la respuesta:
- Verificada: un check verde con «Verificada» y la fecha de emisión, con el estilo de los componentes de etiqueta o insignia de tu sitio.
- Revocada: un estado atenuado o rojo que muestra el motivo de la revocación en lugar de ocultarlo. Tanto los destinatarios como los terceros agradecen un «por qué» honesto antes que un fallo silencioso.
- No encontrada: trátala visualmente igual que «revocada» (no muestres un falso estado de éxito), pero con un texto distinto, porque un ID incorrecto y una revocación deliberada son situaciones diferentes para quien lee la página.
Nada de esto requiere una biblioteca de componentes. Un switch sobre verified / revoked / not_found y tres clases CSS bastan.
Caché, para no saturar el endpoint
El endpoint de verificación es público y no autenticado, lo que también significa que no hay coste en créditos por petición, pero sigue siendo un viaje de red en cada carga de página si lo llamas desde el cliente sin caché. Para una página que muestra la misma insignia a todos los visitantes (una insignia de «Socio verificado» en el pie, por ejemplo), guarda el resultado en caché en el servidor con un TTL corto —de cinco a quince minutos es más que suficiente, ya que las revocaciones no son una señal que cambie rápido— en lugar de volver a pedirlo en cada petición:
const cache = new Map();
async function verifyAwardCached(awardId, ttlMs = 10 * 60 * 1000) {
const hit = cache.get(awardId);
if (hit && Date.now() - hit.at < ttlMs) return hit.result;
const result = await verifyAward(awardId);
cache.set(awardId, { result, at: Date.now() });
return result;
}
Para un widget por visitante (un destinatario que incrusta su propia credencial en un sitio personal), olvídate de la caché y llámalo directamente al cargar la página: la petición extra es insignificante a esa escala.
Lo que badges.ninja ya ofrece a los destinatarios
Antes de construir una integración totalmente a medida, conviene saber qué hay ya disponible de serie. La página pública de cada premio incluye fragmentos listos para compartir e incrustar que los destinatarios pueden copiar directamente, sin escribir código:

Si tu caso de uso es «dejar que un destinatario demuestre que su insignia es auténtica», enlazar a su página badges.ninja/awards/<guid> suele ser más sencillo que construir un widget propio: es la página para personas que vimos antes, y ya gestiona por ti la distinción entre verificada y revocada. Construye el widget a medida de este artículo cuando necesites mostrar el estado de verificación dentro de la interfaz de tu propio producto —un directorio de socios, un panel de auditoría interna, una herramienta de selección de personal que comprueba las certificaciones que declara un candidato—, donde enlazar a otro sitio no es la mejor experiencia.
¿Por qué no un iframe o un widget de proveedor?
Algunas plataformas te dan un fragmento como <iframe src="https://vendor.example.com/verify-widget"> para incrustar el estado de verificación. Funciona, pero tiene costes reales: no puedes adaptar el estilo del contenido al de tu sitio (te quedas con el CSS que el proveedor haya enviado), introduce un documento de origen cruzado que tu Content-Security-Policy tiene que permitir explícitamente, y cada carga de página hace una petición al host del iframe de un tercero, le importe o no al visitante el estado de verificación. Un widget con una sola llamada fetch evita los tres problemas: es tu DOM, es tu estilo y solo se ejecuta cuando tú decides llamarlo.
Recorrer la cadena de verificación completa
Si estás construyendo algo más exhaustivo que un widget de estado —una herramienta de auditoría que revisa un lote de credenciales, por ejemplo, o un informe de cumplimiento—, recorre toda la cadena en lugar de quedarte en la aserción. La aserción referencia una URL badge (el BadgeClass: nombre, descripción, criterios) y el BadgeClass, a su vez, referencia una URL issuer (el perfil de la organización). Un verificador riguroso resuelve las tres y confirma que son coherentes entre sí —la insignia pertenece realmente al emisor que dice, la aserción referencia realmente la insignia que dice—, y no solo que la aserción de nivel superior devuelva un 200. Es más profundidad de la que necesita un widget de estado para una sola credencial, pero conviene conocer esta estructura por si un socio o un auditor pregunta alguna vez: «¿hasta dónde llega realmente vuestra verificación?».
Errores comunes que debes evitar
- Comprobar solo el estado HTTP. Como se ha explicado, una aserción revocada se sigue resolviendo. Lee siempre
assertion.revoked; no deduzcas la verificación solo por el código de estado. - Fijar el ID del premio en un componente compartido. Si el widget debe ser reutilizable en las páginas de muchos destinatarios, recibe el ID del premio como parámetro (un atributo data, una query string, una prop), no como un literal grabado en el script.
- Tragarse los errores de red en silencio. Un
try/catchque no registra nada y muestra el mismo texto de «no encontrada» que ante una insignia realmente inexistente complica la depuración más adelante. Distingue «ha fallado la llamada de red» de «la API ha dicho que no» en tus ramas de error, al menos en los logs. - Volver a verificar en cada renderizado en una app de cliente. Si el widget vive dentro de un componente de React o Vue que se renderiza con frecuencia, verifica una vez por montaje (o una vez por ventana de TTL en caché, según la sección de caché anterior), no en cada nuevo renderizado.
¿Cuesta créditos y funciona en el plan gratuito?
No. El endpoint de verificación queda deliberadamente fuera del sistema de créditos que rige la emisión: comprobar si una insignia es válida no le cuesta nada al emisor, y quien lo llama no necesita cuenta, clave de API ni ningún plan. Eso se cumple tanto si la insignia se emitió en un plan Free, Starter o Pro: la verificación es una propiedad de la propia credencial, no una función limitada por la suscripción del emisor. También es la razón por la que puedes poner este widget con tranquilidad en una página pública con tráfico impredecible (una página de empleo, un directorio de socios) sin preocuparte por límites de uso ligados a tu cuenta: llamas a un endpoint público de solo lectura, no a una ruta de API autenticada.
Una cosa que conviene dejar explícita: el JSON de la aserción que devuelve el endpoint no incluye el correo electrónico en claro del destinatario. El correo se almacena con hash en el backend precisamente para que una respuesta de verificación pública no pueda rastrearse en busca de datos de contacto personales: lo que recibes basta para confirmar la credencial (insignia, emisor, fechas, estado de revocación) sin convertir el endpoint en una herramienta de captación de leads para cualquiera que itere sobre IDs de premios.
¿Listo para emitir tu primera credencial verificable?
Empieza gratis en badges.ninja: diseñador visual, página pública de verificación, certificado en PDF y salida Open Badge v2.0. Sin tarjeta de crédito.
Para ver la estructura anotada completa de cada campo de una aserción, consulta la referencia de campos JSON-LD. Y si emites credenciales mediante programación en lugar de solo verificarlas, la guía de inicio rápido de la API cubre la autenticación y la llamada de creación de premios de la que procede la salida de este endpoint.
Cómo se hizo este artículo
Algunos artículos de este blog se redactan con la ayuda de un asistente de IA y luego son revisados, verificados y editados por el equipo de Badges Ninja antes de publicarse. Cada ejemplo de código y precio se verifica con el producto en vivo. Más información sobre nuestro proceso editorial y de IA en nuestra página de proceso editorial .

Sobre el autor
Nacho Coll
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.
Más de Nacho Coll
- Open Badges en Salesforce: emite credenciales verificables desde los triggers del CRM1 oct 2026 · 10min de lectura
- Emite Open Badges con la API REST de Badges Ninja: guía de inicio rápido28 sept 2026 · 10min de lectura
- Certifier vs Accredible: comparativa de plataformas de certificados (2026)21 sept 2026 · 10min de lectura

