Badge-Verifizierung auf deiner Website einbetten (ohne Iframe, ohne SDK)

Füge deiner Seite ein kleines JavaScript-Snippet (oder einen serverseitigen Fetch) hinzu, das jedes Open Badge über einen öffentlichen JSON-LD-Endpunkt verifiziert. Kein Iframe, kein Tracker von Drittanbietern.

Nacho Coll Von Aktualisiert 9 Min. Lesezeit
Füge deiner Seite ein kleines JavaScript-Snippet (oder einen serverseitigen Fetch) hinzu, das jedes Open Badge über einen öffentlichen JSON-LD-Endpunkt verifiziert. Kein Iframe, kein Tracker von Drittanbietern.

Wenn du Nachweise ausstellst, fragt dich früher oder später jemand: „Woher weiß ich, dass dieses Abzeichen echt ist?“ Die ehrliche Antwort darf nie „vertrau dem PNG“ lauten. Open Badge v2.0 löst das mit einem öffentlichen, maschinenlesbaren Verifizierungs-Endpunkt, und ein „Verifiziert“-Widget auf deiner eigenen Website brauchst du in etwa zehn Zeilen Code — kein Iframe, kein Embed-Skript eines Drittanbieters und kein SDK.

Dieser Beitrag zeigt genau, wie du den Endpunkt aufrufst und das Ergebnis darstellst, in Vanilla JavaScript sowie serverseitig in Node und Python. Außerdem erfährst du, was „Verifizierung“ tatsächlich prüft, wie der Endpunkt neben den menschenlesbaren Seiten steht, die badges.ninja Empfängern bereits bietet, und welche Fehler ein Verifizierungs-Widget fertig aussehen lassen, obwohl es im Stillen nichts beweist.

Was „verifiziert“ wirklich bedeutet

Eine Open-Badge-v2.0-Assertion ist ein JSON-LD-Dokument mit fester Struktur: die Assertion selbst (wer sie erhalten hat, wann), ein Verweis auf die BadgeClass (was es ist, wer die Kriterien definiert) und ein Verweis auf das Aussteller-Profil (wer dafür bürgt). Wenn du ein Abzeichen „verifizierst“, prüfst du eigentlich drei getrennte Dinge:

  1. Existiert die Assertion und lässt sie sich auflösen. Die Award-ID ist echt, nicht geraten oder manipuliert.
  2. Wurde sie widerrufen. Ein Aussteller kann ein Abzeichen nachträglich widerrufen — eine versehentlich ausgestellte Bescheinigung, ein Regelverstoß, eine abgelaufene Zertifizierung — und ein korrekter Prüfer muss das anzeigen, statt nur zu bestätigen, dass der Datensatz existiert.
  3. Hält die Kette zusammen. Die Verweise der Assertion auf Badge und Aussteller führen tatsächlich zu echten, zueinander passenden Datensätzen, nicht zu verwaisten oder inkonsistenten IDs.

Ein Verifizierungs-Widget, das nur eine URL anpingt und auf den Status 200 prüft, übersieht Punkt 2 komplett. Eine widerrufene Assertion lässt sich weiterhin problemlos auflösen — sie trägt lediglich ein Widerrufs-Flag im Payload. Prüft dein Widget nur „war die Anfrage erfolgreich“, wird ein widerrufener Nachweis als verifiziert angezeigt, und das ist schlimmer als gar kein Widget.

Die öffentliche Seite für Menschen vs. der API-Endpunkt

Jeder auf https://badges.ninja ausgestellte Award bekommt eine öffentliche URL, die Empfänger direkt teilen können — zum Ansehen ist keine Anmeldung nötig:

Öffentliche Nachweisseite

Diese Seite verlinken die meisten Empfänger von LinkedIn, aus dem Lebenslauf oder der E-Mail-Signatur — sie ist für Menschen gemacht, zeigt das Abzeichen-Bild, den Aussteller und das Ausstellungsdatum und funktioniert, ohne dass du etwas tun musst. Der unten beschriebene API-Endpunkt liefert dieselbe zugrunde liegende Assertion als strukturiertes JSON-LD — genau das, was du brauchst, wenn du eine eigene Oberfläche baust, statt nur auf unsere zu verlinken.

Der Endpunkt

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

Kein Auth-Header nötig — das ist bewusst öffentlich, denn der ganze Sinn von Open Badges ist, dass jeder (ein Arbeitgeber, ein Recruiter, eine Zulassungsstelle) einen Nachweis ohne Konto verifizieren kann. Alle Details zum Vertrag, einschließlich der genauen Feldnamen in der zurückgegebenen Assertion, findest du in den Verifizierungs-Docs.

Übergib die GUID des Awards, die du entweder aus deinen eigenen Ausstellungsdaten hast oder aus einem badges.ninja/awards/<guid>-Link, den dir ein Empfänger geschickt hat.

Vanilla JS: abrufen und darstellen

Das ist das ganze Widget. Kein Build-Schritt, kein npm-Paket, einfach auf jede Seite packen:

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

Ersetze REPLACE_WITH_AWARD_ID durch eine echte Award-GUID. Das ist die komplette Client-Integration: keine Iframe-Grenze, gegen die du mit CSS kämpfen musst, und kein Drittanbieter-Skript, das neben der Verifizierungsprüfung eigene Analytics oder Tracking-Pixel nachlädt.

Serverseitig: Node

Wenn du lieber im Backend verifizierst — etwa bevor du auf einer von dir kontrollierten Profilseite ein „Verified Partner“-Abzeichen anzeigst oder bevor du in einem Onboarding-Ablauf einen Nachweis als Beleg akzeptierst — ist der Fetch identisch, nur ohne den DOM-Teil:

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

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

Beide Beispiele behandeln eine Antwort ungleich 200 als „nicht gefunden“, statt eine Exception zu werfen. Eine kaputte oder erratene Award-ID ist bei einem öffentlichen Verifizierungs-Endpunkt eine erwartbare Eingabe, kein Ausnahmefall — behandle sie also als normalen Zweig und nicht als abgefangenen Fehler.

Das Ergebnis selbst gestalten, statt fremde UI einzubetten

Da das Widget oben nur ein fetch-Aufruf in dein eigenes DOM ist, gehört dir jedes Pixel. Ein gängiges Muster sind drei visuelle Zustände, direkt aus der Antwort abgeleitet:

  • Verifiziert — ein grüner Haken plus „Verifiziert“ und das Ausstellungsdatum, im Stil der Badge-/Tag-Komponenten deiner Seite.
  • Widerrufen — ein gedämpfter oder roter Zustand, der den Widerrufsgrund zeigt, statt ihn zu verstecken. Empfänger und Dritte profitieren beide von einem ehrlichen „Warum“ statt einem stillen Fehler.
  • Nicht gefunden — visuell wie „widerrufen“ behandeln (zeige keinen falschen Erfolgszustand), aber mit anderem Text, denn eine falsche ID und ein bewusster Widerruf sind für Leser der Seite zwei verschiedene Situationen.

Dafür brauchst du keine Komponentenbibliothek. Ein switch über verified / revoked / not_found und drei CSS-Klassen reichen aus.

Caching, damit du den Endpunkt nicht überlastest

Der Verifizierungs-Endpunkt ist öffentlich und unauthentifiziert, was auch bedeutet, dass keine Credit-Kosten pro Anfrage anfallen — aber bei clientseitigem Aufruf ohne Caching ist es trotzdem bei jedem Seitenaufruf ein Netzwerk-Roundtrip. Wenn eine Seite jedem Besucher dasselbe Abzeichen zeigt (zum Beispiel ein „Verified Partner“-Badge im Footer), cache das Ergebnis serverseitig mit kurzer TTL — fünf bis fünfzehn Minuten reichen locker, denn Widerrufe sind kein schnelllebiges Signal — statt bei jeder Anfrage neu abzurufen:

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

Bei einem Widget pro Besucher (ein Empfänger, der seinen eigenen Nachweis auf einer persönlichen Seite einbettet) kannst du das Caching weglassen und beim Laden der Seite einfach frisch abrufen — die zusätzliche Anfrage fällt in dieser Größenordnung nicht ins Gewicht.

Was badges.ninja Empfängern schon bietet

Bevor du ein komplett eigenes Embed baust, lohnt sich ein Blick darauf, was bereits fertig bereitsteht. Die öffentliche Seite jedes Awards enthält gebrauchsfertige Share- und Embed-Snippets, die Empfänger direkt kopieren können — ganz ohne Code auf ihrer Seite:

Öffentliche Share-Seite mit Social-Buttons + Embed-Snippets

Lautet dein Anwendungsfall „ein Empfänger soll zeigen, dass sein Abzeichen echt ist“, ist der Link auf seine badges.ninja/awards/<guid>-Seite oft einfacher als ein eigenes Widget — es ist die weiter oben gezeigte Seite für Menschen, und sie unterscheidet bereits zwischen verifiziert und widerrufen. Das eigene Widget aus diesem Beitrag baust du, wenn der Verifizierungsstatus in der Oberfläche deines eigenen Produkts erscheinen soll — in einem Partnerverzeichnis, einem internen Audit-Dashboard, einem Recruiting-Tool, das die angegebenen Zertifizierungen eines Kandidaten prüft — wo der Link auf eine andere Seite nicht die richtige UX ist.

Warum kein Iframe oder Vendor-Widget?

Manche Plattformen geben dir ein Snippet wie <iframe src="https://vendor.example.com/verify-widget">, um den Verifizierungsstatus einzubetten. Das funktioniert, hat aber echte Nachteile: Du kannst den Inhalt nicht an deine Seite anpassen (du hängst an dem CSS, das der Anbieter mitliefert), es bringt ein Cross-Origin-Dokument ins Spiel, das deine Content-Security-Policy ausdrücklich erlauben muss, und jeder Seitenaufruf löst eine Anfrage an den Iframe-Host eines Dritten aus, egal ob den Besucher der Verifizierungsstatus überhaupt interessiert. Ein Fetch-Widget mit zwei Aufrufen vermeidet alle drei Punkte — es ist dein DOM, dein Styling, und es läuft nur dann, wenn du es aufrufst.

Die gesamte Verifizierungskette ablaufen

Baust du etwas Gründlicheres als ein Status-Widget — etwa ein Audit-Tool, das einen Stapel von Nachweisen prüft, oder einen Compliance-Bericht —, geh die ganze Kette ab, statt bei der Assertion aufzuhören. Die Assertion verweist auf eine badge-URL (die BadgeClass: Name, Beschreibung, Kriterien), und die BadgeClass verweist ihrerseits auf eine issuer-URL (das Organisationsprofil). Ein gründlicher Prüfer löst alle drei auf und bestätigt, dass sie in sich stimmig sind — das Badge gehört wirklich zum angegebenen Aussteller, die Assertion verweist wirklich auf das angegebene Badge —, statt nur zu prüfen, dass die oberste Assertion ein 200 zurückgibt. Das ist mehr Tiefe, als ein Status-Widget für einen einzelnen Nachweis braucht, aber es lohnt sich zu wissen, dass es diese Struktur gibt, falls ein Partner oder Auditor fragt: „Wie tief geht eure Verifizierung eigentlich?“

Häufige Fehler, die du vermeiden solltest

  • Nur den HTTP-Status prüfen. Wie oben beschrieben, lässt sich eine widerrufene Assertion weiterhin auflösen. Lies immer assertion.revoked, leite die Verifizierung nicht allein aus dem Statuscode ab.
  • Die Award-ID in einer geteilten Komponente fest verdrahten. Soll das Widget auf den Seiten vieler Empfänger wiederverwendbar sein, nimm die Award-ID als Parameter (Data-Attribut, Query-String, Prop) und nicht als Literal, das ins Skript eingebacken ist.
  • Netzwerkfehler stillschweigend verschlucken. Ein try/catch, das nichts loggt und denselben „nicht gefunden“-Text zeigt wie ein wirklich fehlendes Abzeichen, macht späteres Debugging mühsam. Unterscheide in deinen Fehlerzweigen — zumindest in den Logs — zwischen „der Netzwerkaufruf ist fehlgeschlagen“ und „die API hat Nein gesagt“.
  • Bei jedem Render in einer Client-App neu verifizieren. Lebt das Widget in einer React- oder Vue-Komponente, die oft neu rendert, verifiziere einmal pro Mount (oder einmal pro gecachtem TTL-Fenster, wie im Caching-Abschnitt oben), nicht bei jedem Re-Render.

Kostet das Credits, und funktioniert es im Free-Plan?

Nein. Der Verifizierungs-Endpunkt liegt bewusst außerhalb des Credit-Systems, das das Ausstellen regelt — zu prüfen, ob ein Abzeichen gültig ist, kostet den Aussteller nichts, und der Aufrufer braucht dafür weder ein Konto noch einen API-Schlüssel noch irgendeinen Plan. Das gilt unabhängig davon, ob das Abzeichen im Free-, Starter- oder Pro-Plan ausgestellt wurde: Verifizierung ist eine Eigenschaft des Nachweises selbst, kein Feature, das am Abo des Ausstellers hängt. Deshalb kannst du dieses Widget bedenkenlos auf einer öffentlichen Seite mit unvorhersehbarem Traffic einsetzen (eine Karriereseite, ein Partnerverzeichnis), ohne dir Gedanken über Rate Limits zu machen, die an dein Konto gebunden sind — du rufst einen schreibgeschützten, öffentlichen Endpunkt auf, keine authentifizierte API-Route.

Eines solltest du ausdrücklich wissen: Die vom Endpunkt zurückgegebene Assertion-JSON enthält nicht die rohe E-Mail-Adresse des Empfängers. Die Empfänger-E-Mail wird im Backend gehasht gespeichert, genau damit eine öffentliche Verifizierungsantwort nicht nach persönlichen Kontaktdaten durchsucht werden kann — du bekommst genug, um den Nachweis zu bestätigen (Badge, Aussteller, Daten, Widerrufsstatus), ohne dass der Endpunkt zum Lead-Generierungs-Tool für jeden wird, der Award-IDs durchprobiert.

Bereit, deinen ersten verifizierbaren Nachweis auszustellen?

Starte kostenlos auf badges.ninja — visueller Designer, öffentliche Verifizierungsseite, PDF-Zertifikat, Open-Badge-v2.0-Ausgabe. Keine Kreditkarte nötig.

Die vollständig kommentierte Struktur jedes Felds einer Assertion findest du in der JSON-LD-Feldreferenz. Und wenn du Nachweise programmatisch ausstellst, statt sie nur zu verifizieren, behandelt die API-Schnellstartanleitung die Authentifizierung und den Aufruf zum Erstellen von Awards, aus dem die Ausgabe dieses Endpunkts stammt.

Nacho Coll

Über den 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.

Zurück zum Blog

Verwandte Artikel