Indlejr badge-verificering på din hjemmeside (uden iframe og uden SDK)

Tilføj et lille JavaScript-snippet (eller et server-side-kald) til dit site, der verificerer ethvert Open Badge mod et offentligt JSON-LD-endpoint. Ingen iframe, ingen tredjepartstracker.

Nacho Coll Af Opdateret 10 min. læsning
Tilføj et lille JavaScript-snippet (eller et server-side-kald) til dit site, der verificerer ethvert Open Badge mod et offentligt JSON-LD-endpoint. Ingen iframe, ingen tredjepartstracker.

Hvis du udsteder digitale beviser, er der før eller siden nogen, der spørger: “hvordan ved jeg, at det her badge er ægte?” Det ærlige svar bør aldrig være “stol på PNG-filen.” Open Badge v2.0 løser det med et offentligt, maskinlæsbart verificeringsendpoint, og et “Verificeret”-widget på dit eget site kræver cirka ti linjers kode — ingen iframe, intet embed-script fra tredjepart og ingen SDK.

Dette indlæg viser præcis, hvordan du kalder endpointet og viser resultatet, i almindelig JavaScript og på serveren i Node og Python. Det gennemgår også, hvad “verificering” faktisk tjekker, hvordan endpointet passer sammen med de sider til mennesker, som badges.ninja allerede giver modtagerne, og de fejl, der får et verificeringswidget til at se færdigt ud, mens det i al stilfærdighed ikke beviser noget som helst.

Hvad “verificeret” egentlig betyder

En Open Badge v2.0-assertion er et JSON-LD-dokument med en fast struktur: selve assertionen (hvem der modtog den, og hvornår), en reference til BadgeClass (hvad det er, og hvem der definerer kriterierne) og en reference til udstederprofilen (hvem der står inde for den). Når du “verificerer” et badge, tjekker du i virkeligheden tre forskellige ting:

  1. Findes assertionen, og kan den slås op. Award-ID’et er ægte, ikke gættet eller manipuleret.
  2. Er den blevet tilbagekaldt. En udsteder kan tilbagekalde et badge bagefter — et bevis udstedt ved en fejl, en politikovertrædelse, en certificering der er udløbet — og en korrekt verifikator skal vise det, ikke blot bekræfte, at posten findes.
  3. Hænger kæden sammen. Assertionens referencer til badge og udsteder peger faktisk på rigtige, matchende poster, ikke på forældreløse eller uoverensstemmende ID’er.

Et verificeringswidget, der blot pinger en URL og tjekker for statuskode 200, overser punkt 2 fuldstændigt. En tilbagekaldt assertion kan stadig slås op uden problemer — den har bare et tilbagekaldelsesflag i payloaden. Hvis dit widget kun tjekker “lykkedes forespørgslen,” vises et tilbagekaldt bevis som verificeret, og det er værre end slet intet widget.

Den offentlige side til mennesker vs. API-endpointet

Hver award, der udstedes på https://badges.ninja, får en offentlig URL, som modtagerne kan dele direkte, uden at man skal oprette sig for at se den:

Offentlig bevisside

Den side er det, de fleste modtagere vil linke til fra LinkedIn, et CV eller en e-mailsignatur — den er bygget til mennesker, viser badgebilledet, udstederen og udstedelsesdatoen, og kræver ingenting fra dig for at virke. API-endpointet nedenfor returnerer den samme underliggende assertion som struktureret JSON-LD, og det er det, du skal bruge, når du bygger din egen brugerflade i stedet for blot at linke ud til vores.

Endpointet

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

Ingen auth-header nødvendig — det er bevidst offentligt, fordi hele pointen med Open Badges er, at alle (en arbejdsgiver, en rekrutterer, et autorisationsnævn) kan verificere et bevis uden en konto. Alle detaljer om kontrakten, inklusive de præcise feltnavne i den returnerede assertion, findes i verificeringsdokumentationen.

Angiv awardens GUID, som du enten har fra dine egne udstedelsesoptegnelser eller kan udtrække af et badges.ninja/awards/<guid>-link, en modtager har delt med dig.

Vanilla JS: hent og vis

Det her er hele widgetten. Intet build-trin, ingen npm-pakke — smid det direkte ind på en hvilken som helst side:

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

Skift REPLACE_WITH_AWARD_ID ud med en rigtig award-GUID. Det er hele klientintegrationen: ingen iframe-grænse, du skal kæmpe med i CSS, og intet tredjepartsscript, der trækker sin egen analyse eller sporingspixels med ind sammen med verificeringstjekket.

Server-side: Node

Hvis du hellere vil verificere på din backend — for eksempel før du viser et “Verificeret partner”-badge på en profilside, du styrer, eller før du accepterer et bevis som dokumentation i et onboardingforløb — er kaldet identisk, bare uden 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 };
}

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

Begge eksempler behandler et svar, der ikke er 200, som “ikke fundet” i stedet for at kaste en fejl. Et ødelagt eller gættet award-ID er et forventeligt input til et offentligt verificeringsendpoint, ikke et usædvanligt, så håndtér det som en helt normal gren frem for en fanget fejl.

Style resultatet i stedet for at indlejre en andens brugerflade

Fordi widgetten ovenfor blot er et fetch-kald ind i dit eget DOM, ejer du hver eneste pixel. Et almindeligt mønster er tre visuelle tilstande, der afledes direkte af svaret:

  • Verificeret — et grønt flueben plus “Verificeret” og udstedelsesdatoen, stylet så det matcher dit sites badge-/tag-komponenter.
  • Tilbagekaldt — en dæmpet eller rød tilstand, der viser årsagen til tilbagekaldelsen i stedet for at skjule den. Både modtagere og tredjeparter har gavn af et ærligt “hvorfor” frem for en tavs fejl.
  • Ikke fundet — behandl det visuelt på samme måde som “tilbagekaldt” (vis ikke en falsk succestilstand), men med anden tekst, for et ugyldigt ID og en bevidst tilbagekaldelse er to forskellige situationer for den, der læser siden.

Intet af det kræver et komponentbibliotek. En switch på verified / revoked / not_found og tre CSS-klasser dækker det.

Caching, så du ikke hamrer løs på endpointet

Verificeringsendpointet er offentligt og uden autentificering, hvilket også betyder, at der ikke er nogen kreditomkostning pr. forespørgsel at bekymre sig om — men det er stadig en netværksrunde ved hver sideindlæsning, hvis du kalder det på klientsiden uden caching. For en side, der viser det samme badge til alle besøgende (fx et “Verificeret partner”-badge i footeren), så cache resultatet på serveren med en kort TTL — fem til femten minutter er rigeligt, da tilbagekaldelser ikke er et hurtigt skiftende signal — frem for at hente det igen ved hver forespørgsel:

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

Til et widget pr. besøgende (en modtager, der indlejrer sit eget bevis på en personlig side) kan du springe cachen over og blot kalde endpointet friskt ved sideindlæsning — den ekstra forespørgsel er ubetydelig i den skala.

Hvad badges.ninja allerede giver modtagerne

Før du bygger en helt skræddersyet indlejring, er det værd at vide, hvad der allerede er klar til brug. Hver awards offentlige side indeholder færdige del- og indlejringssnippets, som modtagerne kan kopiere direkte, uden at de selv skal skrive kode:

Offentlig delingsside med sociale knapper og indlejringssnippets

Hvis dit behov er “lad en modtager vise, at deres badge er ægte,” er det ofte enklere at linke til deres badges.ninja/awards/<guid>-side end at bygge et brugerdefineret widget — det er den side til mennesker, vi viste tidligere, og den håndterer allerede forskellen på verificeret og tilbagekaldt for dig. Byg det brugerdefinerede widget fra dette indlæg, når du har brug for at vise verificeringsstatussen inde i dit eget produkts brugerflade — et partnerregister, et internt revisions-dashboard, et rekrutteringsværktøj, der tjekker en kandidats angivne certificeringer — hvor det ikke er den rette brugeroplevelse at linke ud til et andet site.

Hvorfor ikke en iframe eller et leverandørwidget?

Nogle platforme giver dig et snippet som <iframe src="https://vendor.example.com/verify-widget"> til at indlejre verificeringsstatus. Det virker, men det har reelle omkostninger: du kan ikke style indholdet, så det matcher dit site (du er bundet af den CSS, leverandøren har leveret), det indfører et cross-origin-dokument, som din Content-Security-Policy eksplicit skal tillade, og hver sideindlæsning sender en forespørgsel til en tredjeparts iframe-vært, uanset om den besøgende overhovedet interesserer sig for verificeringsstatus. Et fetch-widget med to kald undgår alle tre ting — det er dit DOM, din styling, og det kører kun, når du bestemmer, at det skal kaldes.

Gennemgå hele verificeringskæden

Hvis du bygger noget mere grundigt end et statuswidget — et revisionsværktøj, der tjekker en stak beviser, eller en compliance-rapport — så følg hele kæden i stedet for at stoppe ved assertionen. Assertionen refererer til en badge-URL (BadgeClass: navn, beskrivelse, kriterier), og BadgeClass refererer igen til en issuer-URL (organisationens profil). En grundig verifikator slår alle tre op og bekræfter, at de er indbyrdes konsistente — at badget virkelig hører til den udsteder, det påstår, og at assertionen virkelig refererer til det badge, den påstår — ikke blot at assertionen på øverste niveau returnerer 200. Det er mere dybde, end et statuswidget for ét enkelt bevis behøver, men det er godt at kende strukturen, hvis en partner eller revisor engang spørger: “hvor dybt går jeres verificering egentlig?”

Almindelige fejl, du skal undgå

  • Kun at tjekke HTTP-status. Som beskrevet ovenfor kan en tilbagekaldt assertion stadig slås op. Læs altid assertion.revoked, og udled ikke verificering alene ud fra statuskoden.
  • At hardcode award-ID’et i en delt komponent. Hvis widgetten skal kunne genbruges på mange modtageres sider, så tag award-ID’et som parameter (en data-attribut, en query string, en prop) i stedet for en konstant, der er skrevet ind i scriptet.
  • At sluge netværksfejl i tavshed. En try/catch, der ikke logger noget og viser samme “ikke fundet”-tekst som et reelt manglende badge, gør fejlsøgning besværlig senere. Skeln mellem “netværkskaldet fejlede” og “API’et sagde nej” i dine fejlgrene, i det mindste i loggene.
  • At verificere igen ved hver rendering i en klientapp. Hvis widgetten ligger i en React- eller Vue-komponent, der renderes ofte, så verificér én gang pr. mount (eller én gang pr. cachet TTL-vindue, som i caching-afsnittet ovenfor), ikke ved hver gen-rendering.

Koster det credits, og virker det på den gratis plan?

Nej. Verificeringsendpointet ligger bevidst uden for det kreditsystem, der styrer udstedelse — det koster ikke udstederen noget at tjekke, om et badge er gyldigt, og den besøgende, der kalder det, behøver hverken en konto, en API-nøgle eller nogen plan overhovedet. Det gælder uanset om badget er udstedt på en Free-, Starter- eller Pro-plan: verificering er en egenskab ved selve beviset, ikke en funktion, der er låst bag udstederens abonnement. Det er også derfor, du trygt kan sætte widgetten på en offentlig side med uforudsigelig trafik (en karriereside, et partnerregister) uden at bekymre dig om rate limits knyttet til din konto — du kalder et skrivebeskyttet, offentligt endpoint, ikke en autentificeret API-rute.

Én ting er værd at sige eksplicit: Assertion-JSON’en, som endpointet returnerer, indeholder ikke modtagerens rå e-mailadresse. Modtagerens e-mail gemmes hashet i backend netop for at sikre, at et offentligt verificeringssvar ikke kan scrapes for personlige kontaktoplysninger — det, du får tilbage, er nok til at bekræfte beviset (badge, udsteder, datoer, tilbagekaldelsesstatus) uden at gøre endpointet til et værktøj til leadgenerering for enhver, der itererer over award-ID’er.

Klar til at udstede dit første verificerbare bevis?

Kom i gang gratis på badges.ninja — visuel designer, offentlig verificeringsside, PDF-certifikat, Open Badge v2.0-output. Intet kreditkort nødvendigt.

Den fulde kommenterede struktur af hvert felt i en assertion finder du i JSON-LD-feltreferencen. Og hvis du udsteder beviser programmatisk i stedet for blot at verificere dem, dækker API-quickstarten autentificering og det kald til oprettelse af awards, som dette endpoints output stammer fra.

Nacho Coll

Om forfatteren

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.

Tilbage til Bloggen

Relaterede artikler