Salesforce Open Badges: verifieerbare credentials uitgeven vanuit CRM-triggers
Start de uitgifte van Open Badges vanuit elke gebeurtenis op een Salesforce-object — Lead gewonnen, Account geüpgraded, trainingsmijlpaal — met een Apex-callout naar de Badges Ninja API.
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.
De meeste trainingsteams leggen in Salesforce al vast wie wat heeft afgerond. Een cursusinschrijving gaat naar «Completed», een partneraccount bereikt een certificeringsniveau, een klant rondt de onboarding af. Het record verandert, en een week later exporteert iemand een lijst en mailt handmatig certificaten.
Deze gids laat zien hoe je dat gat dicht: zodra een record de status bereikt die je wilt, roept Salesforce de API van https://badges.ninja aan, wordt er een Open Badge v2.0-credential uitgegeven en wordt de verificatielink terug op het record opgeslagen. Die link kun je daarna in een e-mailtemplate, een rapport of een Experience Cloud-pagina zetten.
Je hebt een Salesforce-beheerder (of een developer sandbox) nodig, een badge die al is ontworpen op https://badges.ninja en een API-sleutel. Heb je de API nog nooit aangeroepen, lees dan eerst de API-quickstart; dat kost tien minuten.
Wanneer een CRM-trigger het juiste gereedschap is
Controleer voordat je Apex schrijft of je het wel nodig hebt. Geef je credentials in batches uit, één keer per semester of kwartaal, dan is de CSV-upload van het dashboard sneller en heb je helemaal geen code nodig. De Salesforce-route loont als:
- De uitgifte moet plaatsvinden op het moment dat een record verandert, zonder menselijke tussenstap.
- De gegevens over «wie het behaald heeft» al in Salesforce staan en je geen tweede bron van waarheid wilt.
- Je de credentiallink wilt bewaren op de Contact, Lead of een eigen trainingsrecord, voor rapportage.
Is je workflowtool Zapier in plaats van Apex, dan werkt dezelfde API-aanroep daar ook; zie Open Badges-automatisering met Zapier.
Wat je gaat bouwen
De flow bestaat uit vier onderdelen:
- Een badge ontworpen in de visuele designer. Kopieer de id (de
iddieGET /badgesteruggeeft). - Een API-sleutel aangemaakt op de pagina API Keys van je dashboard. De sleutel wordt maar één keer getoond, dus bewaar hem meteen.
- Een Named Credential in Salesforce die de sleutel bevat, zodat die nooit in code voorkomt.
- Een Apex-trigger plus een Queueable-klasse die de award uitgeeft en de awardlink terugschrijft naar het record.

Stap 1: bewaar de sleutel in een Named Credential
Een API-sleutel hardcoden in Apex is de fout die je moet vermijden. Maak in Setup een External Credential met een aangepaste header, en daarna een Named Credential die naar de API-host verwijst:
- URL:
https://api.badges.ninja - Naam van de aangepaste header:
X-Api-Key - Waarde van de aangepaste header: je sleutel, in de vorm
bws_YOUR_KEY_HERE - Naam van de Named Credential:
BadgesNinja
Geef de permission set die je automatisering uitvoert toegang tot de principal van de External Credential. Met een Named Credential heb je geen aparte Remote Site Setting nodig; Salesforce autoriseert de callout via de credential. Gebruikt je org nog legacy Named Credentials of een gewoon endpoint, voeg dan https://api.badges.ninja toe als Remote Site Setting en stuur de header vanuit de code mee.
Stap 2: velden aan het record toevoegen
Voeg op het object dat de prestatie vertegenwoordigt (wij gebruiken een custom object met de naam Training_Enrollment__c) het volgende toe:
Badge_Id__c(Text): de badge die je uitgeeft. Door dit per cursus op te slaan kan één trigger veel badges bedienen.Badge_Award_Id__c(Text, unique): de award-id die de API teruggeeft.Badge_Url__c(URL): de openbare verificatiepagina.Badge_Error__c(Long Text): de laatste fout, zodat beheerders mislukkingen zien zonder debuglogs te openen.
Het veld Badge_Award_Id__c heeft een dubbele functie. Het is je bescherming tegen dubbele uitgifte: de trigger geeft alleen uit als het leeg is.
Stap 3: de Apex-callout
Een trigger kan niet rechtstreeks een callout doen, omdat callouts niet zijn toegestaan in dezelfde transactie als nog niet gecommitte DML. Geef het werk door aan een Queueable die Database.AllowsCallouts implementeert.
Het API-contract voor POST /awards is kort. De body bevat badgeId, een geneste recipient-object met name en email, en een verplichte issuedOn. Een geslaagde aanroep geeft HTTP 200 terug met { "awardId": "..." }. De naam van de ontvanger moet minstens vijf tekens lang zijn, dus een Contact met de naam «Al Li» wordt geweigerd; valideer dus voordat je aanroept.
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;
}
}
Twee details om op te letten. Ten eerste is issuedOn in epoch-milliseconden, precies wat Datetime.getTime() teruggeeft. Ten tweede bevat het antwoord niet de verificatie-URL, dus bouwt de code die op uit de award-id: https://badges.ninja/awards/<awardId>.
Stap 4: de trigger
Houd de trigger dun. Hij beslist alleen welke records in aanmerking komen en zet per record één job in de wachtrij.
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));
}
}
}
De wasCompleted-controle is belangrijk. Zonder die controle zou elke latere wijziging aan een afgerond record de logica opnieuw starten. Samen met de bescherming tegen een lege award-id in de job kan een record maar één credential opleveren.
Eén limiet om te respecteren: Salesforce staat maar een klein aantal Queueable-jobs toe vanuit één transactie (50 in synchrone context). Laad je afrondingen in bulk, bijvoorbeeld een data-import van 2.000 rijen, dan loopt de trigger hierboven tegen dat plafond aan. Upload bij bulkladingen de lijst liever via de CSV-bulkawards van het dashboard, of stap over op een Batchable-klasse met Database.AllowsCallouts die een paar records per scope verwerkt.

Foutafhandeling en nieuwe pogingen
De API geeft standaard statuscodes terug, en elke code vraagt om een andere reactie van je code:
| Status | Betekenis | Wat te doen |
|---|---|---|
| 400 | Een veld ontbrak of was ongeldig, inclusief een ontvangernaam van minder dan vijf tekens | Niet opnieuw proberen. Corrigeer de data en schrijf het bericht naar Badge_Error__c. |
| 401 | Onjuiste of ontbrekende API-sleutel | Controleer de Named Credential. Opnieuw proberen helpt pas als dit is opgelost. |
| 402 | Geen credentials of quotum meer in je abonnement | Stop met uitgeven en waarschuw een beheerder; probeer opnieuw na een upgrade of na de reset van het quotum. |
| 404 | De badgeId bestaat niet | Corrigeer Badge_Id__c op het record of de cursus. |
| 5xx of timeout | Tijdelijke storing | Later veilig opnieuw te proberen. |
Omdat de job de fout naar het record schrijft en Badge_Award_Id__c leeg laat, is een nieuwe poging zo simpel als een selectievakje omzetten of het record opnieuw opslaan. Een kleine geplande Apex-klasse die zoekt naar afgeronde inschrijvingen zonder award-id en met een tijdelijke fout, en die opnieuw in de wachtrij zet, geeft je een automatische retry-wachtrij zonder extra infrastructuur. Begrens het aantal pogingen met een tellerveld, zodat een blijvend foutief record niet eindeloos blijft lussen.
Controleer je abonnement voordat je dit aan productie koppelt. Het Free-plan dekt 1.200 credentials per jaar; Starter kost $9 per maand en Pro $29 per maand. Een trigger die bij elke Lead afgaat verbruikt een klein plan snel, dus beperk de criteria tot echte prestaties.
De credential zichtbaar maken in Salesforce
Zodra Badge_Url__c is gevuld, kun je het overal gebruiken:
- E-mailmeldingen en templates: voeg het veld samen in een e-mail «Gefeliciteerd, hier is je credential».
- Rapporten: een rapport op
Badge_Award_Id__c != nullgeeft je het aantal uitgiftes per cursus, regio of accounteigenaar. - Experience Cloud: toon de link op de certificeringspagina van een partner, zodat kopers het zelf kunnen verifiëren.
- Page layouts: voeg het URL-veld toe zodat support «heeft ze haar badge gekregen?» in één oogopslag kan beantwoorden.
De verificatiepagina is openbaar en vereist geen aanmelding, en dat is nu juist de bedoeling. Een ontvanger, een wervingsmanager of de inkoopafdeling van een klant kan de link openen en bevestigen wie de credential heeft uitgegeven, wat deze certificeert en wanneer. Voor machineleesbare controles is de Open Badge v2.0 JSON-LD van elke award beschikbaar op https://api.badges.ninja/certify-badge/award/<awardId>.
Ontvangers krijgen ook de gebruikelijke extra’s: een unieke verificatie-URL, een QR-code, een A4-pdfcertificaat en een optie Add to LinkedIn vanuit het ontvangersportaal, waar je inlogt met een magic link in plaats van een wachtwoord.
Waarom dit beter is dan een badge-plugin uit AppExchange
Badge-plugins in AppExchange worden vaak geprijsd per Salesforce-gebruiker of per managed-package-licentie. Die kosten schalen mee met de grootte van je Salesforce-team, niet met het aantal credentials dat je uitgeeft. Een trainingsteam van tien mensen en een programma met tienduizend leerlingen betalen dus heel verschillend voor dezelfde output.
Een API-integratie draait dat model om. Je betaalt op basis van credentialvolume met een vast plan, de triggercode kun je zelf lezen en aanpassen, en de output is standaard Open Badge v2.0, zodat ontvangers niet vastzitten aan een eigen viewer. Ook behoud je je ontwerpvrijheid: de visuele designer biedt 80+ templates, vormen, iconen en eigen uploads, in plaats van een vaste lay-out binnen een package.
De eerlijke afweging: een plugin installeer je met één klik en vraagt geen Apex. Als niemand in je team een paar tientallen regels code kan onderhouden, past een plugin of een no-code connector misschien beter. Vergelijk je platforms in het algemeen in plaats van integraties, dan zet het overzicht over het Credly-alternatief prijzen en functies naast elkaar.
Testen voordat je live gaat
Draai de hele flow eerst in een sandbox.
- Maak een testbadge en een testontvanger met een e-mailadres dat je zelf beheert.
- Implementeer in Apex-tests
HttpCalloutMockdie status 200 en{"awardId":"test-award-id"}teruggeeft; Salesforce staat geen echte callouts toe in een testcontext, en die dekking heb je nodig om te deployen. - Zet in de sandbox één inschrijving op Completed en controleer of
Badge_Award_Id__cenBadge_Url__cbinnen een minuut worden gevuld. - Open de URL in een privévenster om te bevestigen dat de openbare verificatiepagina zonder inloggen wordt weergegeven.
- Forceer een fout (maak de badge-id leeg, of gebruik een ontvangernaam van minder dan vijf tekens) en controleer dat de fout in
Badge_Error__cterechtkomt.
Roteer de API-sleutel als hij ooit in een debuglog of screenshot lekt. Intrekken gaat direct vanaf de pagina API Keys, en je hoeft alleen de External Credential bij te werken, niet je code.
Afronding
Het patroon is klein: een dunne trigger bepaalt wanneer, een Queueable doet de callout, een Named Credential bewaart de sleutel en drie velden op het record leggen de uitkomst vast. Het werkt voor trainingsmijlpalen, partnerniveaus, evenementaanwezigheid of elke statuswijziging die je in Salesforce kunt uitdrukken.
Begin met één object en één badge, volg het een week en breid uit zodra het retry-gedrag bij je data past.
Klaar om je eerste verifieerbare credential uit te geven? Begin gratis op badges.ninja — visuele designer, openbare verificatiepagina, pdfcertificaat, Open Badge v2.0-output. Geen creditcard nodig.
Hoe dit artikel is gemaakt
Sommige berichten op deze blog worden met behulp van een AI-assistent opgesteld en vervolgens door het Badges Ninja-team gecontroleerd, geverifieerd en bewerkt voordat ze worden gepubliceerd. Elk codevoorbeeld en elke prijs wordt geverifieerd aan de hand van het live product. Lees meer over ons redactionele en AI-proces op onze pagina over ons redactieproces .

Over de auteur
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.

