Bädda in badge-verifiering på din webbplats (utan iframe, utan SDK)

Lägg till ett litet JavaScript-snippet (eller ett fetch på serversidan) på din sajt som verifierar vilken Open Badge som helst mot en offentlig JSON-LD-endpoint. Ingen iframe, ingen spårare från tredje part.

Nacho Coll Av Uppdaterad 10 min läsning
Lägg till ett litet JavaScript-snippet (eller ett fetch på serversidan) på din sajt som verifierar vilken Open Badge som helst mot en offentlig JSON-LD-endpoint. Ingen iframe, ingen spårare från tredje part.

Om du utfärdar meriter kommer någon förr eller senare att fråga: ”Hur vet jag att den här badgen är äkta?” Det ärliga svaret ska aldrig vara ”lita på PNG-filen”. Open Badge v2.0 löser det med en offentlig, maskinläsbar verifieringsendpoint, och att lägga en ”Verifierad”-widget på din egen sajt tar ungefär tio rader kod — ingen iframe, inget inbäddningsskript från tredje part och ingen SDK.

Det här inlägget visar exakt hur du anropar endpointen och visar resultatet, i ren JavaScript och på serversidan i Node och Python. Det går också igenom vad ”verifiering” egentligen kontrollerar, var endpointen passar in bredvid de mänskliga sidor som badges.ninja redan ger mottagarna, och de misstag som får en verifieringswidget att se färdig ut medan den i tysthet inte bevisar något alls.

Vad ”verifierad” egentligen betyder

En Open Badge v2.0-assertion är ett JSON-LD-dokument med fast struktur: själva assertionen (vem som fick den, när), en referens till BadgeClass (vad den är, vem som definierar kriterierna) och en referens till utfärdarprofilen (vem som går i god för den). När du ”verifierar” en badge kontrollerar du i själva verket tre separata saker:

  1. Finns assertionen och går den att slå upp. Utmärkelsens ID är äkta, inte gissat eller manipulerat.
  2. Har den återkallats. En utfärdare kan återkalla en badge i efterhand — en merit som utfärdats av misstag, ett regelbrott, ett certifikat som löpt ut — och en korrekt verifierare måste visa det, inte bara bekräfta att posten finns.
  3. Hänger kedjan ihop. Assertionens referenser till badge och utfärdare pekar på verkliga, matchande poster, inte på föräldralösa eller felaktiga ID:n.

En verifieringswidget som bara pingar en URL och kollar efter statusen 200 missar punkt 2 helt. En återkallad assertion går fortfarande att slå upp utan problem — den har bara en återkallningsflagga i innehållet. Om din widget bara kontrollerar ”lyckades anropet” kommer en återkallad merit att visas som verifierad, vilket är värre än att inte ha någon widget alls.

Den offentliga sidan för människor vs. API-endpointen

Varje utmärkelse som utfärdas på https://badges.ninja får en offentlig URL som mottagarna kan dela direkt, utan att du behöver registrera dig för att se den:

Public credential page

Det är den sidan de flesta mottagare länkar till från LinkedIn, ett CV eller en e-postsignatur — den är byggd för människor, visar badgens bild, utfärdaren och utfärdandedatumet, och kräver ingenting av dig för att fungera. API-endpointen nedan returnerar samma underliggande assertion som strukturerad JSON-LD, vilket är vad du vill ha när du bygger ditt eget gränssnitt i stället för att bara länka till vårt.

Endpointen

GET https://api.badges.ninja/certify-badge/award/{guid}

Ingen auth-header krävs — den är avsiktligt offentlig, eftersom hela poängen med Open Badges är att vem som helst (en arbetsgivare, en rekryterare, ett licensieringsorgan) ska kunna verifiera en merit utan konto. Fullständig kontraktsinformation, inklusive de exakta fältnamnen i den returnerade assertionen, finns i verifieringsdokumentationen.

Skicka in utmärkelsens GUID, som du antingen har från dina egna utfärdandeposter eller plockar ut ur en badges.ninja/awards/<guid>-länk som en mottagare delat med dig.

Ren JS: hämta och rendera

Det här är hela widgeten. Inget byggsteg, inget npm-paket — släpp in den direkt på vilken sida som helst:

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

Byt ut REPLACE_WITH_AWARD_ID mot ett riktigt GUID för en utmärkelse. Det är hela integrationen på klientsidan: ingen iframe-gräns att slåss med i CSS, och inget skript från tredje part som drar med sig egen analys eller spårningspixlar vid sidan av verifieringen.

Serversidan: Node

Om du hellre verifierar i din backend — till exempel innan du visar en ”Verifierad partner”-badge på en profilsida du kontrollerar, eller innan du godtar en merit som bevis i ett onboardingflöde — är anropet identiskt, minus DOM-delen:

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

Serversidan: 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")}

Båda exemplen behandlar ett svar som inte är 200 som ”hittades inte” i stället för att kasta ett fel. Ett trasigt eller gissat utmärkelse-ID är en förväntad indata för en offentlig verifieringsendpoint, inte ett undantag, så hantera det som en vanlig gren och inte som ett fångat fel.

Styla resultatet i stället för att bädda in någon annans gränssnitt

Eftersom widgeten ovan bara är ett fetch-anrop in i din egen DOM äger du varje pixel. Ett vanligt mönster är tre visuella tillstånd som mappas direkt från svaret:

  • Verifierad — en grön bock plus ”Verifierad” och utfärdandedatumet, stylat så att det matchar din sajts badge-/tagg-komponenter.
  • Återkallad — ett dämpat eller rött tillstånd som visar återkallningsorsaken i stället för att dölja den. Både mottagare och tredje parter har nytta av ett ärligt ”varför”, inte ett tyst fel.
  • Hittades inte — behandla det visuellt som ”återkallad” (visa inget falskt framgångstillstånd), men med annan text, eftersom ett felaktigt ID och en medveten återkallelse är olika situationer för den som läser sidan.

Inget av detta kräver ett komponentbibliotek. En switch på verified / revoked / not_found och tre CSS-klasser räcker.

Cachning, så att du inte hamrar endpointen

Verifieringsendpointen är offentlig och oautentiserad, vilket också betyder att det inte finns någon kreditkostnad per anrop — men det är ändå en nätverksrunda vid varje sidladdning om du anropar den på klientsidan utan cache. För en sida som visar samma badge för alla besökare (till exempel en ”Verifierad partner”-badge i sidfoten) bör du cacha resultatet på serversidan med en kort TTL — fem till femton minuter räcker gott, eftersom återkallelser inte är en snabbrörlig signal — i stället för att hämta på nytt vid varje förfrågan:

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

För en widget per besökare (en mottagare som bäddar in sin egen merit på en personlig sajt) kan du hoppa över cachen och anropa direkt vid sidladdning — det extra anropet är försumbart i den skalan.

Vad badges.ninja redan ger mottagarna

Innan du bygger en helt anpassad inbäddning är det värt att veta vad som redan finns direkt. Varje utmärkelses offentliga sida innehåller färdiga delnings- och inbäddningssnippets som mottagarna kan kopiera rakt av, utan någon kod på sin sida:

Public share page with social buttons + embed snippets

Om ditt användningsfall är ”låt en mottagare visa att deras badge är äkta” är det ofta enklare att länka till deras badges.ninja/awards/<guid>-sida än att bygga en egen widget — det är den mänskliga sidan som visades tidigare, och den hanterar redan skillnaden mellan verifierad och återkallad åt dig. Bygg den anpassade widgeten från det här inlägget när du behöver visa verifieringsstatusen inuti din egen produkts gränssnitt — en partnerkatalog, en intern granskningspanel, ett rekryteringsverktyg som kontrollerar en kandidats uppgivna certifieringar — där det inte är rätt användarupplevelse att länka vidare till en annan sajt.

Varför inte en iframe eller en leverantörswidget?

Vissa plattformar ger dig ett snippet som <iframe src="https://vendor.example.com/verify-widget"> för att bädda in verifieringsstatus. Det fungerar, men har verkliga kostnader: du kan inte styla om innehållet så att det matchar din sajt (du sitter fast med den CSS leverantören levererat), det inför ett cross-origin-dokument som din Content-Security-Policy uttryckligen måste tillåta, och varje sidladdning gör ett anrop till en tredje parts iframe-värd oavsett om besökaren alls bryr sig om verifieringsstatus. En fetch-widget med två anrop undviker alla tre — det är din DOM, din styling, och den körs bara när du bestämmer dig för att anropa den.

Att följa hela verifieringskedjan

Om du bygger något mer grundligt än en statuswidget — ett granskningsverktyg som kontrollerar en omgång meriter, eller en efterlevnadsrapport — följ hela kedjan i stället för att stanna vid assertionen. Assertionen refererar till en badge-URL (BadgeClass: namn, beskrivning, kriterier) och BadgeClass refererar i sin tur till en issuer-URL (organisationens profil). En rigorös verifierare slår upp alla tre och bekräftar att de är internt konsekventa — badgen tillhör verkligen den utfärdare den påstår, assertionen refererar verkligen till den badge den påstår — inte bara att assertionen på toppnivå returnerar 200. Det är mer djup än en widget för en enskild merit behöver, men det är bra att veta att strukturen finns om en partner eller revisor någon gång frågar ”hur djupt går er verifiering egentligen?”

Vanliga misstag att undvika

  • Att bara kontrollera HTTP-statusen. Som beskrivits ovan går en återkallad assertion fortfarande att slå upp. Läs alltid assertion.revoked, dra inte slutsatsen att något är verifierat enbart utifrån statuskoden.
  • Att hårdkoda utmärkelse-ID:t i en delad komponent. Om widgeten ska kunna återanvändas på många mottagares sidor ska utmärkelse-ID:t tas emot som parameter (ett data-attribut, en query string, en prop), inte som en literal inbakad i skriptet.
  • Att svälja nätverksfel tyst. Ett try/catch som inte loggar något och visar samma ”hittades inte”-text som för en badge som verkligen saknas gör felsökning smärtsam senare. Skilj på ”nätverksanropet misslyckades” och ”API:et sa nej” i dina felgrenar, åtminstone i loggarna.
  • Att verifiera om vid varje rendering i en klientapp. Om widgeten bor i en React- eller Vue-komponent som renderas om ofta ska du verifiera en gång per mount (eller en gång per cachad TTL-period, enligt cachningsavsnittet ovan), inte vid varje omrendering.

Kostar det krediter, och fungerar det på gratisplanen?

Nej. Verifieringsendpointen ligger avsiktligt utanför det kreditsystem som styr utfärdande — att kontrollera om en badge är giltig kostar utfärdaren ingenting, och besökaren som anropar den behöver inget konto, ingen API-nyckel eller någon plannivå alls. Det gäller oavsett om badgen utfärdades på en Free-, Starter- eller Pro-plan: verifiering är en egenskap hos själva meriten, inte en funktion som styrs av utfärdarens prenumeration. Det är också därför du tryggt kan lägga den här widgeten på en offentlig sida med oförutsägbar trafik (en karriärsida, en partnerkatalog) utan att oroa dig för hastighetsgränser knutna till ditt konto — du anropar en skrivskyddad, offentlig endpoint, inte en autentiserad API-rutt.

En sak är värd att säga rakt ut: assertion-JSON som endpointen returnerar innehåller inte mottagarens e-postadress i klartext. Mottagarens e-post lagras hashad i backend just för att ett offentligt verifieringssvar inte ska kunna skrapas på personuppgifter — det du får tillbaka räcker för att bekräfta meriten (badge, utfärdare, datum, återkallningsstatus) utan att göra endpointen till ett leadgenereringsverktyg för den som itererar över utmärkelse-ID:n.

Redo att utfärda din första verifierbara merit?

Börja gratis på badges.ninja — visuell designer, offentlig verifieringssida, PDF-certifikat, Open Badge v2.0-utdata. Inget kreditkort krävs.

För den fullständiga kommenterade strukturen för varje fält i en assertion, se JSON-LD-fältreferensen. Och om du utfärdar meriter programmatiskt i stället för att bara verifiera dem, går API-snabbstarten igenom autentisering och anropet som skapar utmärkelsen, som den här endpointens utdata kommer från.

Nacho Coll

Om författaren

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.

Tillbaka till Bloggen

Relaterade artiklar