Open Badges en Salesforce: emite credenciales verificables desde los triggers del CRM

Lanza la emisión de Open Badges desde cualquier evento de un objeto de Salesforce (Lead ganado, cuenta mejorada, hito de formación) con un callout de Apex a la API de Badges Ninja.

Nacho Coll Por Actualizado 10 min de lectura
Lanza la emisión de Open Badges desde cualquier evento de un objeto de Salesforce (Lead ganado, cuenta mejorada, hito de formación) con un callout de Apex a la API de Badges Ninja.

La mayoría de los equipos de formación ya registran en Salesforce quién terminó qué. Una inscripción a un curso pasa a “Completado”, una cuenta de partner alcanza un nivel de certificación, un cliente termina el onboarding. El registro cambia y, una semana después, alguien exporta una lista y envía los certificados por correo a mano.

Esta guía te muestra cómo cerrar esa brecha: cuando un registro llega al estado que te interesa, Salesforce llama a la API de https://badges.ninja, se emite una credencial Open Badge v2.0 y el enlace de verificación se guarda de vuelta en el registro. Después puedes poner ese enlace en una plantilla de correo, un informe o una página de Experience Cloud.

Necesitarás un administrador de Salesforce (o un sandbox de desarrollo), una insignia ya diseñada en https://badges.ninja y una clave de API. Si nunca has llamado a la API, lee primero la guía rápida de la API; te lleva diez minutos.

Cuándo un trigger del CRM es la herramienta adecuada

Antes de escribir Apex, comprueba si de verdad lo necesitas. Si emites credenciales por lotes una vez por periodo o por trimestre, la carga de CSV del panel es más rápida y no requiere nada de código. La vía de Salesforce se justifica cuando:

  • La emisión debe ocurrir en el momento en que cambia un registro, sin ningún paso manual.
  • Los datos de “quién lo obtuvo” ya viven en Salesforce y no quieres una segunda fuente de verdad.
  • Quieres guardar el enlace de la credencial en el Contacto, el Lead o el registro de formación personalizado para generar informes.

Si tu herramienta de flujos es Zapier en lugar de Apex, la misma llamada a la API funciona allí también; consulta automatización de Open Badges con Zapier.

Qué vas a construir

El flujo tiene cuatro piezas:

  1. Una insignia diseñada en el diseñador visual. Copia su id (el id que devuelve GET /badges).
  2. Una clave de API creada en la página de claves de API de tu panel. La clave se muestra una sola vez, así que guárdala de inmediato.
  3. Una Named Credential en Salesforce que guarde la clave, para que nunca aparezca en el código.
  4. Un trigger de Apex más una clase Queueable que emite el premio y escribe el enlace del premio de vuelta en el registro.

Newly created API key revealed once

Paso 1: guarda la clave en una Named Credential

Dejar una clave de API escrita a mano en Apex es el error que debes evitar. En Setup, crea una External Credential con una cabecera personalizada y luego una Named Credential que apunte al host de la API:

  • URL: https://api.badges.ninja
  • Nombre de la cabecera personalizada: X-Api-Key
  • Valor de la cabecera personalizada: tu clave, con la forma bws_YOUR_KEY_HERE
  • Nombre de la Named Credential: BadgesNinja

Concede al permission set que ejecuta tu automatización acceso al principal de la External Credential. Con una Named Credential no necesitas un Remote Site Setting aparte; Salesforce autoriza el callout a través de la credencial. Si tu org todavía usa Named Credentials heredadas o un endpoint simple, añade https://api.badges.ninja como Remote Site Setting y envía la cabecera desde el código.

Paso 2: añade campos al registro

En el objeto que representa el logro (usaremos un objeto personalizado llamado Training_Enrollment__c), añade:

  • Badge_Id__c (Text): la insignia que se va a emitir. Guardarla por curso permite que un único trigger sirva para muchas insignias.
  • Badge_Award_Id__c (Text, único): el id del premio que devuelve la API.
  • Badge_Url__c (URL): la página pública de verificación.
  • Badge_Error__c (Long Text): el último error, para que los administradores vean los fallos sin abrir los logs de depuración.

El campo Badge_Award_Id__c cumple una doble función. Es tu protección contra duplicados: el trigger solo emite cuando está vacío.

Paso 3: el callout de Apex

Un trigger no puede hacer un callout directamente, porque no se permiten callouts en la misma transacción que un DML sin confirmar. Pasa el trabajo a un Queueable que implemente Database.AllowsCallouts.

El contrato de la API para POST /awards es corto. El cuerpo lleva badgeId, un objeto recipient anidado con name y email, y un issuedOn obligatorio. Una llamada correcta devuelve HTTP 200 con { "awardId": "..." }. El nombre del destinatario debe tener al menos cinco caracteres, así que un Contacto llamado “Al Li” sería rechazado; valida antes de llamar.

public class BadgeIssueJob implements Queueable, Database.AllowsCallouts {
    private Id enrollmentId;
    public BadgeIssueJob(Id enrollmentId) { this.enrollmentId = enrollmentId; }

    public void execute(QueueableContext ctx) {
        Training_Enrollment__c e = [
            SELECT Id, Badge_Id__c, Badge_Award_Id__c, Contact__r.Name, Contact__r.Email
            FROM Training_Enrollment__c WHERE Id = :enrollmentId LIMIT 1
        ];
        if (e.Badge_Award_Id__c != null) { return; }   // already issued

        Map<String, Object> recipient = new Map<String, Object>{
            'name' => e.Contact__r.Name,
            'email' => e.Contact__r.Email
        };
        Map<String, Object> body = new Map<String, Object>{
            'badgeId' => e.Badge_Id__c,
            'recipient' => recipient,
            'issuedOn' => Datetime.now().getTime()
        };

        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:BadgesNinja/awards');
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setBody(JSON.serialize(body));

        HttpResponse res = new Http().send(req);
        if (res.getStatusCode() == 200) {
            Map<String, Object> out = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            String awardId = (String) out.get('awardId');
            e.Badge_Award_Id__c = awardId;
            e.Badge_Url__c = 'https://badges.ninja/awards/' + awardId;
            e.Badge_Error__c = null;
        } else {
            e.Badge_Error__c = res.getStatusCode() + ': ' + res.getBody();
        }
        update e;
    }
}

Dos detalles que merece la pena notar. Primero, issuedOn son milisegundos de época, que es exactamente lo que devuelve Datetime.getTime(). Segundo, la respuesta no incluye la URL de verificación, así que el código la construye a partir del id del premio: https://badges.ninja/awards/<awardId>.

Paso 4: el trigger

Mantén el trigger delgado. Solo decide qué registros cumplen los criterios y encola un job por registro.

trigger TrainingEnrollmentBadge on Training_Enrollment__c (after insert, after update) {
    for (Training_Enrollment__c e : Trigger.new) {
        Boolean completed = e.Status__c == 'Completed';
        Boolean wasCompleted = Trigger.isUpdate && Trigger.oldMap.get(e.Id).Status__c == 'Completed';
        if (completed && !wasCompleted && e.Badge_Award_Id__c == null && e.Badge_Id__c != null) {
            System.enqueueJob(new BadgeIssueJob(e.Id));
        }
    }
}

La comprobación wasCompleted importa. Sin ella, cualquier edición posterior de un registro completado volvería a disparar la lógica. Combinada con la protección del id de premio vacío dentro del job, un registro solo puede producir una credencial.

Un límite que debes respetar: Salesforce permite encolar un número reducido de jobs Queueable desde una sola transacción (50 en contexto síncrono). Si cargas finalizaciones de forma masiva, por ejemplo una importación de datos de 2.000 filas, el trigger anterior chocará con ese techo. Para cargas masivas, sube la lista mediante los premios masivos por CSV del panel, o cambia a una clase Batchable con Database.AllowsCallouts que procese unos pocos registros por scope.

API keys list showing the masked prefix

Gestión de errores y reintentos

La API devuelve códigos de estado estándar, y cada uno pide una respuesta distinta de tu código:

EstadoSignificadoQué hacer
400Faltaba un campo o no era válido, incluido un nombre de destinatario de menos de cinco caracteresNo reintentes. Corrige los datos y escribe el mensaje en Badge_Error__c.
401Clave de API incorrecta o ausenteRevisa la Named Credential. Reintentar no servirá hasta que se corrija.
402Sin credenciales o cuota en tu planDeja de emitir y avisa a un administrador; reintenta tras mejorar el plan o cuando se restablezca la cuota.
404El badgeId no existeCorrige Badge_Id__c en el registro o el curso.
5xx o timeoutFallo transitorioEs seguro reintentar más tarde.

Como el job escribe el error en el registro y deja Badge_Award_Id__c vacío, reintentar es tan simple como marcar una casilla o volver a guardar el registro. Una pequeña clase de Apex programada que busque inscripciones completadas sin id de premio y con un error transitorio, y las vuelva a encolar, te da una cola de reintentos automática sin infraestructura adicional. Limita los intentos con un campo contador para que un registro permanentemente defectuoso no entre en bucle infinito.

Revisa tu plan antes de conectar esto a producción. El plan Free cubre 1.200 credenciales al año; Starter cuesta $9 al mes y Pro $29 al mes. Un trigger que se dispare con cada Lead consumiría un plan pequeño enseguida, así que acota los criterios a logros reales.

Haz visible la credencial en Salesforce

Una vez que Badge_Url__c está rellenado, puedes usarlo donde quieras:

  • Alertas y plantillas de correo: inserta el campo en un correo de “Enhorabuena, aquí tienes tu credencial”.
  • Informes: un informe sobre Badge_Award_Id__c != null te da el número de emisiones por curso, región o propietario de cuenta.
  • Experience Cloud: muestra el enlace en la página de certificación de un partner para que los compradores puedan verificarlo por sí mismos.
  • Page layouts: añade el campo de URL para que el personal de soporte pueda responder a “¿recibió ella su insignia?” de un vistazo.

La página de verificación es pública y no requiere registro, que es precisamente la idea. Un destinatario, un responsable de contratación o el equipo de compras de un cliente puede abrir el enlace y confirmar quién emitió la credencial, qué certifica y cuándo. Para comprobaciones legibles por máquinas, el JSON-LD de Open Badge v2.0 de cualquier premio está disponible en https://api.badges.ninja/certify-badge/award/<awardId>.

Los destinatarios también obtienen los extras habituales: una URL de verificación única, un código QR, un certificado PDF en A4 y la opción de añadir a LinkedIn desde el portal del destinatario, que inicia sesión con un enlace mágico en lugar de una contraseña.

Por qué esto supera a un plugin de insignias de AppExchange

Los plugins de insignias de AppExchange suelen tarificarse por asiento de Salesforce o por licencia de paquete gestionado. Ese coste crece con el tamaño de tu equipo de Salesforce, no con el número de credenciales que emites, así que un equipo de formación de diez personas y un programa de diez mil alumnos pagan de forma muy distinta por el mismo resultado.

Una integración por API invierte ese modelo. Pagas por volumen de credenciales en un plan fijo, el código del trigger es tuyo para leerlo y cambiarlo, y el resultado es Open Badge v2.0 estándar, así que los destinatarios no quedan atados a un visor propietario. También conservas tu libertad de diseño: el diseñador visual ofrece más de 80 plantillas, formas, iconos y cargas personalizadas, en lugar de un diseño fijo dentro de un paquete.

La contrapartida honesta: un plugin se instala con un clic y no necesita Apex. Si nadie en tu equipo puede mantener unas pocas decenas de líneas de código, un plugin o un conector sin código puede ser la mejor opción. Si estás comparando plataformas en general y no integraciones, el análisis de alternativa a Credly cubre precios y diferencias de funciones lado a lado.

Pruebas antes de salir a producción

Ejecuta primero todo el flujo en un sandbox.

  1. Crea una insignia de prueba y un destinatario de prueba con una dirección de correo que controles.
  2. En los tests de Apex, implementa HttpCalloutMock que devuelva estado 200 y {"awardId":"test-award-id"}; Salesforce no permite callouts reales en contexto de test, y necesitas esa cobertura para desplegar.
  3. En el sandbox, pon una inscripción en Completed y comprueba que Badge_Award_Id__c y Badge_Url__c se rellenan en menos de un minuto.
  4. Abre la URL en una ventana de navegador privada para confirmar que la página pública de verificación se muestra sin iniciar sesión.
  5. Fuerza un fallo (deja en blanco el id de la insignia, o usa un nombre de destinatario de menos de cinco caracteres) y confirma que el error llega a Badge_Error__c.

Rota la clave de API si alguna vez se filtra en un log de depuración o en una captura de pantalla. La revocación es inmediata desde la página de claves de API, y solo tienes que actualizar la External Credential, no tu código.

Para terminar

El patrón es pequeño: un trigger delgado decide cuándo, un Queueable hace el callout, una Named Credential guarda la clave y tres campos del registro almacenan el resultado. Sirve para hitos de formación, niveles de partner, asistencia a eventos o cualquier cambio de estado que puedas expresar en Salesforce.

Empieza con un objeto y una insignia, obsérvalo durante una semana y amplíalo cuando el comportamiento de los reintentos encaje con tus datos.

¿Listo para emitir tu primera credencial verificable? Empieza gratis en badges.ninja — diseñador visual, página pública de verificación, certificado PDF, salida Open Badge v2.0. Sin 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