Osadź weryfikację odznaki na swojej stronie (bez iframe, bez SDK)

Dodaj do swojej strony mały fragment JavaScriptu (albo pobieranie danych po stronie serwera), który weryfikuje dowolną odznakę Open Badge w publicznym endpoincie JSON-LD. Bez iframe, bez zewnętrznego trackera.

Nacho Coll Autor Zaktualizowano 9 min czytania
Dodaj do swojej strony mały fragment JavaScriptu (albo pobieranie danych po stronie serwera), który weryfikuje dowolną odznakę Open Badge w publicznym endpoincie JSON-LD. Bez iframe, bez zewnętrznego trackera.

Jeśli wystawiasz certyfikaty, prędzej czy później ktoś zapyta: „skąd mam wiedzieć, że ta odznaka jest prawdziwa?”. Uczciwa odpowiedź nigdy nie powinna brzmieć „zaufaj plikowi PNG”. Open Badge v2.0 rozwiązuje ten problem za pomocą publicznego, czytelnego maszynowo endpointu weryfikacji, a umieszczenie widżetu „Zweryfikowano” na własnej stronie to około dziesięciu linijek kodu — bez iframe, bez zewnętrznego skryptu do osadzania i bez SDK.

Ten wpis pokazuje dokładnie, jak wywołać endpoint i wyświetlić wynik — w czystym JavaScripcie oraz po stronie serwera w Node i Pythonie. Wyjaśnia też, co tak naprawdę sprawdza „weryfikacja”, jak endpoint wpisuje się w strony dla ludzi, które badges.ninja już daje odbiorcom, oraz jakie błędy sprawiają, że widżet weryfikacji wygląda na gotowy, a po cichu niczego nie dowodzi.

Co naprawdę oznacza „zweryfikowano”

Asercja Open Badge v2.0 to dokument JSON-LD o stałej strukturze: sama asercja (kto ją otrzymał i kiedy), odwołanie do BadgeClass (czym jest odznaka i kto definiuje kryteria) oraz odwołanie do profilu wystawcy (kto za nią ręczy). Gdy „weryfikujesz” odznakę, w rzeczywistości sprawdzasz trzy osobne rzeczy:

  1. Czy asercja istnieje i da się ją odczytać. Identyfikator nagrody jest prawdziwy, a nie zgadnięty ani zmanipulowany.
  2. Czy została unieważniona. Wystawca może unieważnić odznakę po fakcie — certyfikat wystawiony przez pomyłkę, naruszenie zasad, wygasła certyfikacja — a poprawny weryfikator musi to pokazać, a nie tylko potwierdzić, że rekord istnieje.
  3. Czy cały łańcuch się zgadza. Odwołania asercji do odznaki i wystawcy faktycznie prowadzą do prawdziwych, pasujących do siebie rekordów, a nie do osieroconych lub niedopasowanych identyfikatorów.

Widżet, który tylko odpytuje URL i sprawdza status 200, w ogóle pomija punkt 2. Unieważniona asercja nadal się poprawnie odczytuje — po prostu ma w danych flagę unieważnienia. Jeśli Twój widżet sprawdza tylko, „czy żądanie się powiodło”, unieważniony certyfikat zostanie wyświetlony jako zweryfikowany, a to gorsze niż brak widżetu w ogóle.

Publiczna strona dla ludzi a endpoint API

Każda nagroda wystawiona na https://badges.ninja dostaje publiczny adres URL, który odbiorcy mogą udostępniać bezpośrednio, a jego obejrzenie nie wymaga rejestracji:

Public credential page

Na tę stronę większość odbiorców będzie linkować z LinkedIna, CV lub stopki e-maila — jest stworzona dla ludzi, pokazuje obraz odznaki, wystawcę i datę wystawienia, a do działania nie potrzebuje niczego z Twojej strony. Endpoint API poniżej zwraca tę samą asercję jako ustrukturyzowany JSON-LD, czego potrzebujesz, gdy budujesz własny interfejs zamiast po prostu linkować do naszego.

Endpoint

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

Nagłówek autoryzacji nie jest wymagany — endpoint jest celowo publiczny, bo cały sens Open Badges polega na tym, że każdy (pracodawca, rekruter, komisja licencyjna) może zweryfikować certyfikat bez konta. Pełne szczegóły kontraktu, w tym dokładne nazwy pól zwracanej asercji, znajdziesz w dokumentacji weryfikacji.

Przekaż GUID nagrody, który masz albo z własnych rekordów wystawień, albo wyciągniesz z linku badges.ninja/awards/<guid> udostępnionego Ci przez odbiorcę.

Czysty JS: pobierz i wyświetl

To cały widżet. Bez kroku budowania, bez pakietu npm — wklej go na dowolną stronę:

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

Zamień REPLACE_WITH_AWARD_ID na prawdziwy GUID nagrody. To cała integracja po stronie klienta: bez granicy iframe, z którą trzeba walczyć w CSS, i bez zewnętrznego skryptu, który obok sprawdzenia weryfikacji dociąga własną analitykę czy piksele śledzące.

Po stronie serwera: Node

Jeśli wolisz weryfikować na backendzie — na przykład zanim wyświetlisz odznakę „Zweryfikowany partner” na kontrolowanej przez siebie stronie profilu albo zanim uznasz certyfikat za dowód w procesie wdrożenia — pobieranie wygląda tak samo, tylko bez części związanej z DOM:

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

Po stronie serwera: 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")}

Oba przykłady traktują odpowiedź inną niż 200 jako „nie znaleziono”, zamiast rzucać wyjątek. Uszkodzony lub zgadnięty identyfikator nagrody to w publicznym endpoincie weryfikacji oczekiwane dane wejściowe, a nie sytuacja wyjątkowa, więc obsłuż go jako zwykłą gałąź, a nie przechwycony błąd.

Stylizuj wynik zamiast osadzać cudzy interfejs

Ponieważ powyższy widżet to po prostu wywołanie fetch zapisujące wynik do Twojego własnego DOM, masz kontrolę nad każdym pikselem. Typowy wzorzec to trzy stany wizualne odwzorowane wprost z odpowiedzi:

  • Zweryfikowano — zielony znacznik, napis „Zweryfikowano” i data wystawienia, w stylu pasującym do komponentów odznak/tagów na Twojej stronie.
  • Unieważniono — stonowany lub czerwony stan pokazujący powód unieważnienia zamiast go ukrywać. Zarówno odbiorcy, jak i osoby trzecie zyskują na uczciwym „dlaczego”, a nie na cichej porażce.
  • Nie znaleziono — wizualnie traktuj to tak samo jak „unieważniono” (nie renderuj fałszywego stanu sukcesu), ale z innym komunikatem, bo błędny identyfikator i celowe unieważnienie to różne sytuacje dla osoby czytającej stronę.

Nic z tego nie wymaga biblioteki komponentów. Wystarczy switch na verified / revoked / not_found i trzy klasy CSS.

Cache, żeby nie zasypywać endpointu

Endpoint weryfikacji jest publiczny i nie wymaga uwierzytelnienia, co oznacza też, że nie ma kosztu kredytów za żądanie — ale jeśli wywołujesz go po stronie klienta bez cache, to wciąż jest wywołanie sieciowe przy każdym załadowaniu strony. Dla strony, która pokazuje tę samą odznakę każdemu odwiedzającemu (na przykład odznakę „Zweryfikowany partner” w stopce), buforuj wynik po stronie serwera z krótkim TTL — pięć do piętnastu minut w zupełności wystarczy, bo unieważnienia nie są szybko zmieniającym się sygnałem — zamiast pobierać dane przy każdym żądaniu:

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

W przypadku widżetu dla pojedynczego odwiedzającego (odbiorca osadzający własny certyfikat na osobistej stronie) pomiń cache i po prostu wywołuj endpoint przy każdym załadowaniu strony — dodatkowe żądanie jest w tej skali pomijalne.

Co badges.ninja już daje odbiorcom

Zanim zbudujesz w pełni własne osadzenie, warto wiedzieć, co jest dostępne od ręki. Publiczna strona każdej nagrody zawiera gotowe fragmenty do udostępniania i osadzania, które odbiorcy mogą skopiować bez pisania kodu:

Public share page with social buttons + embed snippets

Jeśli Twój przypadek użycia to „pozwolić odbiorcy pokazać, że jego odznaka jest prawdziwa”, linkowanie do jego strony badges.ninja/awards/<guid> jest często prostsze niż budowanie własnego widżetu — to wspomniana wcześniej strona dla ludzi i już sama rozróżnia stan zweryfikowany od unieważnionego. Własny widżet z tego wpisu zbuduj wtedy, gdy status weryfikacji ma być wyświetlany wewnątrz interfejsu Twojego produktu — w katalogu partnerów, wewnętrznym panelu audytu, narzędziu rekrutacyjnym sprawdzającym deklarowane certyfikaty kandydata — gdzie odsyłanie na inną stronę nie jest właściwym UX.

Dlaczego nie iframe ani widżet dostawcy?

Niektóre platformy dają fragment w rodzaju <iframe src="https://vendor.example.com/verify-widget"> do osadzania statusu weryfikacji. Działa, ale ma realne koszty: nie możesz zmienić wyglądu zawartości, by pasowała do Twojej strony (jesteś skazany na CSS dostarczony przez dostawcę), wprowadza dokument z innej domeny, który Twoja Content-Security-Policy musi jawnie dopuścić, a każde załadowanie strony generuje żądanie do hosta iframe strony trzeciej, niezależnie od tego, czy odwiedzającego w ogóle obchodzi status weryfikacji. Widżet oparty na jednym wywołaniu fetch unika wszystkich trzech problemów — to Twój DOM, Twoje style i uruchamia się tylko wtedy, gdy zdecydujesz się go wywołać.

Przejście całego łańcucha weryfikacji

Jeśli budujesz coś dokładniejszego niż widżet statusu — na przykład narzędzie audytowe sprawdzające partię certyfikatów albo raport zgodności — przejdź cały łańcuch, zamiast zatrzymywać się na asercji. Asercja odwołuje się do adresu URL badge (BadgeClass: nazwa, opis, kryteria), a BadgeClass z kolei do adresu URL issuer (profil organizacji). Rygorystyczny weryfikator odczytuje wszystkie trzy i potwierdza ich wzajemną spójność — odznaka naprawdę należy do wystawcy, którego wskazuje, a asercja naprawdę odwołuje się do odznaki, którą wskazuje — a nie tylko to, że asercja najwyższego poziomu zwraca 200. To więcej, niż potrzebuje widżet statusu pojedynczego certyfikatu, ale warto znać tę strukturę na wypadek, gdyby partner lub audytor zapytał: „jak głęboko sięga Wasza weryfikacja?”.

Typowe błędy, których warto unikać

  • Sprawdzanie wyłącznie statusu HTTP. Jak wyżej: unieważniona asercja nadal się odczytuje. Zawsze czytaj assertion.revoked, nie wnioskuj o weryfikacji wyłącznie z kodu statusu.
  • Zahardkodowany identyfikator nagrody we wspólnym komponencie. Jeśli widżet ma być wielokrotnego użytku na stronach wielu odbiorców, przekaż identyfikator nagrody jako parametr (atrybut data, query string, prop), a nie literał wpisany w skrypcie.
  • Ciche połykanie błędów sieciowych. Blok try/catch, który nic nie loguje i pokazuje ten sam tekst „nie znaleziono” co przy naprawdę nieistniejącej odznace, utrudnia późniejsze debugowanie. Rozróżniaj w gałęziach błędów „wywołanie sieciowe się nie powiodło” od „API odmówiło”, przynajmniej w logach.
  • Ponowna weryfikacja przy każdym renderze w aplikacji klienckiej. Jeśli widżet żyje w komponencie React lub Vue, który często się renderuje, weryfikuj raz na zamontowanie (albo raz na okno TTL cache, zgodnie z sekcją o cache), a nie przy każdym ponownym renderze.

Czy to kosztuje kredyty i czy działa w planie Free?

Nie. Endpoint weryfikacji jest celowo poza systemem kredytów, który reguluje wystawianie — sprawdzenie, czy odznaka jest ważna, nic nie kosztuje wystawcy i nie wymaga od odwiedzającego, który go wywołuje, konta, klucza API ani żadnego planu. Dotyczy to odznak wystawionych w planie Free, Starter i Pro: weryfikacja jest właściwością samego certyfikatu, a nie funkcją zależną od subskrypcji wystawcy. Dlatego możesz bezpiecznie umieścić ten widżet na publicznej stronie z nieprzewidywalnym ruchem (strona kariery, katalog partnerów), nie martwiąc się o limity powiązane z Twoim kontem — wywołujesz publiczny endpoint tylko do odczytu, a nie uwierzytelnioną trasę API.

Warto wyraźnie zaznaczyć jedno: asercja JSON zwracana przez endpoint nie zawiera surowego adresu e-mail odbiorcy. E-mail odbiorcy jest przechowywany w backendzie w postaci hasha właśnie po to, by z publicznej odpowiedzi weryfikacji nie dało się wyciągać danych kontaktowych — to, co dostajesz, wystarcza do potwierdzenia certyfikatu (odznaka, wystawca, daty, status unieważnienia), bez zamieniania endpointu w narzędzie do pozyskiwania leadów dla każdego, kto iteruje po identyfikatorach nagród.

Gotowy, by wystawić pierwszy weryfikowalny certyfikat?

Zacznij za darmo na badges.ninja — wizualny kreator, publiczna strona weryfikacji, certyfikat PDF, wyjście Open Badge v2.0. Bez karty kredytowej.

Pełną opisaną strukturę każdego pola asercji znajdziesz w opisie pól JSON-LD. A jeśli wystawiasz certyfikaty programowo, a nie tylko je weryfikujesz, krótki przewodnik po API opisuje uwierzytelnianie i wywołanie tworzące nagrodę, z którego pochodzą dane zwracane przez ten endpoint.

Nacho Coll

O autorze

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.

Powrót do bloga

Powiązane artykuły