Open Badges uitgeven vanuit WordPress (zonder zware plugin)

Geef Open Badges uit vanuit elke WordPress cursus-/LMS-plugin (LearnDash, LifterLMS, Tutor) door bij voltooiing de Badges Ninja API aan te roepen — geen aparte badge-plugin nodig.

Nacho Coll Door Bijgewerkt 12 min leestijd
Geef Open Badges uit vanuit elke WordPress cursus-/LMS-plugin (LearnDash, LifterLMS, Tutor) door bij voltooiing de Badges Ninja API aan te roepen — geen aparte badge-plugin nodig.

Als je cursussen op WordPress draait, ken je het wel: iemand rondt een module af in LearnDash, LifterLMS of Tutor LMS, de LMS markeert het als “voltooid” en dan… gebeurt er niets verifieerbaars. Misschien mail je een PDF. Misschien installeer je een speciale WordPress badge-plugin die zijn eigen databasetabellen toevoegt, zijn eigen afbeeldingenbibliotheek en zijn eigen idee van wat een “badge” is — een die niemand buiten je site kan verifiëren.

Er is een eenvoudigere weg. WordPress-cursusplugins activeren al een action hook op het moment dat iemand een cursus voltooit. Je kunt die hook opvangen en één HTTP-aanroep doen naar de Badges Ninja API. Geen extra plugin, geen extra hosting, geen eigen badgeformaat — gewoon een standaardconforme Open Badge v2.0-credential die je ontvanger overal kan verifiëren, aan LinkedIn kan toevoegen en als PDF kan downloaden.

Dit artikel laat het patroon één keer zien en toont daarna de specifieke hook voor LearnDash, LifterLMS en Tutor LMS.

Waarom niet gewoon een WordPress badge-plugin gebruiken?

Speciale WordPress badge-plugins lossen een kleiner probleem op dan het lijkt. Ze zijn goed in het tonen van een afbeelding op een “mijn prestaties”-pagina binnen je site. Ze zijn meestal veel zwakker in de dingen die er echt toe doen voor een credential:

  • Overdraagbaarheid. Een badge die alleen bestaat als shortcode op je WordPress-site is geen credential — het is decoratie. Zodra de ontvanger van baan verandert, van mailbox wisselt, of je site offline gaat, verdwijnt het “bewijs” mee.
  • Verificatie. Iedereen zou moeten kunnen controleren of een credential echt is zonder in te loggen op je WordPress-beheer. Open Badge v2.0 geeft je standaard een openbare verificatie-URL en machineleesbare JSON-LD.
  • Onderhoudsoppervlak. Elke WordPress-plugin is weer iets om te patchen, weer een databasetabel om te back-uppen, weer een extra faalpunt tijdens een core-update.

Door bij het voltooien van een cursus een externe API aan te roepen, omzeil je alle drie deze problemen. WordPress blijft verantwoordelijk voor waar het goed in is — contentlevering en voortgangsregistratie — terwijl de credential zelf leeft in een systeem dat specifiek is gebouwd om deze uit te geven, te verifiëren en te delen.

Het patroon: één hook, één HTTP-aanroep

Elke grote WordPress LMS-plugin activeert een actie wanneer een gebruiker een cursus voltooit. De integratie bestaat altijd uit dezelfde drie stappen:

  1. Haak in op de voltooiingsactie van jouw specifieke LMS.
  2. Zoek de identiteit van de ontvanger op (of maak deze aan) — meestal gewoon het e-mailadres en de weergavenaam van hun WordPress-account.
  3. Roep POST /awards aan op de Badges Ninja API met wp_remote_post(), waarbij je het badge-ID en de gegevens van de ontvanger meegeeft.

Eerst heb je een API-sleutel nodig. Genereer er een vanuit je dashboard onder API Keys — zie de handleiding voor API-sleutels voor de volledige uitleg. Sleutels worden bij aanmaak eenmalig getoond, dus sla de sleutel op in wp-config.php als constante in plaats van in de database:

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

Nieuw aangemaakte sleutel eenmalig getoond

Houd deze sleutel serverside. Alles in deze handleiding draait in PHP op je WordPress-server — wp_remote_post() is een serverside aanroep, en wp-config.php wordt nooit naar de browser gestuurd. Dat is precies wat je wilt: de sleutel reist server-naar-server over HTTPS, en geen enkele sitebezoeker kan hem lezen. Verplaats deze aanroep nooit naar client-side JavaScript — een fetch() vanuit de browser met de sleutel erin zou hem voor iedereen zichtbaar maken via de devtools van hun browser. Twee gewoontes die de moeite waard zijn: zorg dat je host wp-config.php als uitgevoerde PHP serveert (nooit als platte tekst, en laat geen wp-config.php.bak-kopie in de webroot staan), en maak een speciale API-sleutel voor deze integratie zodat je hem onafhankelijk kunt intrekken als hij ooit gecompromitteerd raakt — zonder je andere tools te breken.

Een gedeelde helperfunctie houdt elke LMS-hook slank:

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 );
}

Het e-mailadres van de ontvanger wordt serverside SHA-256-gehasht zodra het Badges Ninja bereikt, en de retourpayload bevat de verificatie-URL van de toekenning — handig als je meteen in WordPress een link “bekijk je credential” wilt tonen nadat de aanroep is geslaagd.

LearnDash

LearnDash activeert learndash_course_completed met één array-argument dat user en course bevat:

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 );
} );

Het badge-ID opslaan als cursusmeta (in plaats van één globale constante BADGES_NINJA_BADGE_ID) is de juiste keuze zodra je meer dan één cursus hebt — elke cursus koppelt aan zijn eigen badgeontwerp.

LifterLMS

LifterLMS activeert lifterlms_course_completed met $user_id en $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 activeert tutor_course_complete_after met het cursus-ID, en je haalt de huidige gebruiker op uit de sessie:

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 );
} );

Als jouw Tutor LMS-opzet voltooiing vanuit een achtergrondproces activeert (bijvoorbeeld via automatisch nakijken van een quiz), vervang dan get_current_user_id() door het gebruikers-ID dat jouw specifieke hook aanlevert — controleer de payload eenmalig tijdens de opzet met error_log( print_r( func_get_args(), true ) ) om de argumenten te bevestigen.

Het badge ontwerpen voordat je de hook aansluit

Voordat dit allemaal in werking treedt, heb je een badge nodig om toe te kennen. De visuele ontwerper dekt dit zonder dat je een designer in dienst hoeft te hebben — meer dan 80 vormsjablonen, aangepaste kleuren, een iconenbibliotheek en logo-upload, allemaal met snap-to-grid. Ben je nieuw met de ontwerper, dan loodst Je eerste verifieerbare digitale certificaat ontwerpen je door het kiezen van een sjabloon en het opleveren van iets dat ontvangers ook echt willen delen.

Zodra de badge bestaat, haal je het ID op uit het dashboard (of via GET /badges) en zet je het in het aangepaste veld van je cursus.

Wat de ontvanger daadwerkelijk krijgt

Dit is het deel dat een zelfgebouwde WordPress badge-plugin niet kan nabootsen. Als POST /awards slaagt, krijgt de ontvanger:

  • Een unieke verificatie-URL die iedereen kan openen — geen WordPress-login vereist — die laat zien dat de credential echt is, door wie hij is uitgegeven en wanneer.
  • Een QR-code en een A4 PDF-certificaat, automatisch gegenereerd.
  • Een knop Toevoegen aan LinkedIn-profiel, als je uitgever een LinkedIn-organisatie-ID heeft ingesteld — zie Een LinkedIn “Toevoegen aan profiel”-knop toevoegen voor de eenmalige instelling.
  • Toegang tot hun eigen ontvangersportaal op badges.ninja/me, via magic-link-inloggen — geen wachtwoord om te vergeten, geen apart account om te beheren op je WordPress-site.
  • Standaardconforme Open Badge v2.0 JSON-LD, zodat elke OB-bewuste verifier (niet alleen Badges Ninja) hem kan valideren.

Niets daarvan is aan WordPress om te bouwen, en niets daarvan zou alleen in je WordPress-database moeten leven.

Fouten netjes afhandelen

Cursusvoltooiingshooks worden geactiveerd tijdens een live paginaverzoek, en een trage of mislukte API-aanroep mag het “gefeliciteerd”-scherm van de cursist niet blokkeren. Twee praktische aanpassingen zodra je verder gaat dan een proof of concept:

Laat een mislukte toekenning de UI niet blokkeren. De helperfunctie hierboven logt al en geeft false terug in plaats van een fout te gooien — LearnDash, LifterLMS en Tutor gaan allemaal gewoon door met hun normale voltooiingsflow, ongeacht wat badges_ninja_issue_award() teruggeeft.

Zet het in een wachtrij in plaats van inline aan te roepen, op schaal. Als je grote cohorten tegelijk door één cursus laat lopen (een startdatum voor een cohort, het vrijgeven van een webinarreplay), activeer dan vanuit de hook een geplande WP-Cron-gebeurtenis in plaats van de API synchroon aan te roepen:

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 );

Dit spreidt de belasting en voorkomt dat één trage aanvraag het probleem van de cursist wordt. Voor echt grote batches (een hele voorgaande cohort, in één keer gemigreerd), sla de WordPress-hookroute volledig over en gebruik bulktoekenningen via CSV — upload één keer, en het platform pagineert de uitgifte serverside met ingebouwde pauze/hervatten.

Een opmerking over idempotentie

Als je LMS ooit de voltooiingshook meer dan één keer activeert voor dezelfde gebruiker (een veelvoorkomend randgeval bij LearnDash met herkansingen van quizzen, of een Tutor LMS-hersynchronisatie), wil je niet dezelfde badge tweemaal toekennen. De eenvoudigste bescherming is een WordPress user-meta-vlag die je direct na een geslaagde aanroep instelt:

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 );
}

Als je op echte schaal uitgeeft en dit centraal wilt afhandelen in plaats van per site, bekijk dan webhook-gestuurde afhandeling van events of de volledige API-quickstart voor de vorm van verzoeken/antwoorden bij elk endpoint.

Wanneer dit patroon groter wordt dan één cursus

De voorbeelden hierboven gaan uit van één badge per cursus. Zodra je stapelbare credentials gebruikt — een trackbadge die ontgrendelt na meerdere cursusbadges, of een afsluitende badge die afhankelijk is van meerdere vereisten — verplaats je de koppeling “welke badge, bij welke voltooiing” van post-meta naar een kleine opzoektabel, of stuur je dit aan vanuit een op een spreadsheet gebaseerde automatisering (Airtable of Google Sheets) die dezelfde POST /awards-aanroep activeert. De API-aanroep zelf verandert niet; alleen wat bepaalt wanneer je die doet, verandert.

Problemen met de integratie oplossen

Een handvol problemen veroorzaakt het meeste van de wrijving die mensen ervaren wanneer ze dit voor het eerst opzetten:

De hook wordt nooit geactiveerd. Controleer of je de juiste actie voor de daadwerkelijke versie van je plugin gebruikt — LearnDash heeft met name de namen van voltooiingshooks veranderd tussen grote releases (oudere installaties vertrouwen soms nog op learndash_course_completed die vanuit een ander codepad wordt geactiveerd dan bij nieuwere). Voeg tijdelijk een error_log() toe bovenaan je callback en volg wp-content/debug.log terwijl je een testcursus voltooit met een wegwerpgebruiker.

wp_remote_post() loopt vast op een timeout. Shared-hostingomgevingen blokkeren soms uitgaande HTTPS naar onbekende hosts, of beperken requesttimeouts tot onder de hierboven ingestelde 15 seconden. Als aanroepen consequent vastlopen op een timeout, controleer dan bij je host of uitgaande verbindingen naar api.badges.ninja zijn toegestaan, en bevestig dat je PHP curl-extensie actuele CA-certificaten heeft — een verouderde certificaatbundel is een veelvoorkomende stille storing op oudere shared-hostingstacks.

400 Bad Request. De payload mist een verplicht veld of heeft de verkeerde vorm. POST /awards verwacht badgeId, een geneste recipient-object met name en email, en een issuedOn-tijdstempel — de helper hierboven stuurt precies dat. De meest voorkomende valkuil is de naam van de ontvanger: die moet minstens 5 tekens lang zijn, dus een WordPress-weergavenaam zoals “Jo” wordt afgewezen. Val terug op de volledige naam van het account (of het lokale deel van het e-mailadres) wanneer de weergavenaam te kort is.

401 Unauthorized. De API-sleutelconstante wordt niet geladen, of is opnieuw gegenereerd nadat de constante was ingesteld. Sleutels worden bij aanmaak eenmalig getoond en kunnen niet opnieuw worden opgehaald — als je niet meer weet welke sleutel actief is, verwijder dan de verouderde en geef een nieuwe uit in plaats van te gokken.

Badge uitgegeven, maar met het verkeerde ontwerp. Dit is bijna altijd een verouderde badges_ninja_badge_id post-meta-waarde die is achtergebleven na testen. Omdat de koppeling op het cursusbericht staat, vergeet je gemakkelijk dat je die hebt gewijzigd nadat je een cursus voor een nieuwe cohort hebt gedupliceerd — de moeite waard om even te controleren telkens als je een cursus kloont.

Dubbele toekenningen voor dezelfde persoon. Hierboven al behandeld met de user-meta-bescherming, maar het is de moeite waard om te herhalen: het is niet gegarandeerd dat LMS-voltooiingshooks precies één keer worden geactiveerd. Behandel “al toegekend” als een normaal geval om op te controleren, niet als een randgeval om te negeren.

Veelgestelde vragen

Werkt dit met WooCommerce-cursusbundels? Ja — als een bundelaankoop een badge moet activeren los van cursusvoltooiing (bijvoorbeeld een “gecertificeerd lid”-badge voor iedereen die een specifieke bundel koopt), haak dan in op woocommerce_order_status_completed in plaats van een LMS-voltooiingsactie, en zoek het badge-ID op vanuit de productmeta in plaats van vanuit een cursus.

Kunnen ontvangers de badge krijgen zonder WordPress-account? Ja, en dat is precies de bedoeling. De toekenning is gekoppeld aan hun e-mailadres in Badges Ninja, niet aan een WordPress-gebruikersrecord. Als je site gastafrekenen of anonieme quizvoltooiing toestaat, zorg er dan gewoon voor dat je een echt e-mailadres vastlegt om door te geven aan badges_ninja_issue_award().

Wat als ik toekenningen wil beoordelen voordat ze worden verstuurd? Activeer de aanroep niet direct vanuit de voltooiingshook. Schrijf in plaats daarvan de openstaande toekenning (gebruiker, cursus, tijdstempel) naar een aangepaste tabel of posttype, en geef hem uit vanuit een geplande taak of een admin-knop “Goedkeuren & toekennen” die dezelfde helperfunctie op aanvraag aanroept.

Heb ik een apart badge per cursus nodig, of kan één badge er meerdere dekken? Beide werken. Sommige uitgevers maken één badge per cursus voor meer granulariteit; anderen maken één badge “programma voltooid” en kennen die pas toe zodra elke vereiste cursus is afgerond, waarbij ze binnen de hook controleren of aan de vereisten is voldaan voordat ze de API aanroepen.


Klaar om je eerste verifieerbare credential uit te geven? Start gratis op badges.ninja — visuele ontwerper, openbare verificatiepagina, PDF-certificaat, Open Badge v2.0-output. Geen creditcard vereist.

Nacho Coll

Over de auteur

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.

Terug naar blog

Gerelateerde artikelen