Integrează verificarea insignelor pe site-ul tău (fără iframe, fără SDK)

Adaugă pe site un mic fragment de JavaScript (sau un fetch pe server) care verifică orice Open Badge printr-un endpoint public JSON-LD. Fără iframe, fără tracker de la terți.

Nacho Coll De Actualizat 10 min de citit
Adaugă pe site un mic fragment de JavaScript (sau un fetch pe server) care verifică orice Open Badge printr-un endpoint public JSON-LD. Fără iframe, fără tracker de la terți.

Dacă emiți credențiale, mai devreme sau mai târziu cineva te va întreba: „De unde știu că această insignă e reală?” Răspunsul sincer nu ar trebui să fie niciodată „ai încredere în PNG”. Open Badge v2.0 rezolvă problema printr-un endpoint public de verificare, care poate fi citit de mașini, iar un widget „Verificat” pe propriul site înseamnă cam zece linii de cod — fără iframe, fără script de embed de la terți și fără SDK.

Acest articol îți arată exact cum apelezi endpointul și cum afișezi rezultatul, în JavaScript simplu și pe server, în Node și Python. Vei afla și ce verifică de fapt „verificarea”, unde se încadrează endpointul alături de paginile pentru oameni pe care badges.ninja le oferă deja destinatarilor, plus greșelile care fac ca un widget de verificare să pară gata, deși nu dovedește aproape nimic.

Ce înseamnă de fapt „verificat”

O asertiune Open Badge v2.0 este un document JSON-LD cu o structură fixă: asertiunea propriu-zisă (cine a primit-o, când), o referință către BadgeClass (ce este, cine definește criteriile) și o referință către profilul emitentului (cine garantează pentru ea). Când „verifici” o insignă, de fapt controlezi trei lucruri distincte:

  1. Există asertiunea și se rezolvă? ID-ul premiului este real, nu inventat sau modificat.
  2. A fost revocată? Un emitent poate revoca o insignă ulterior — o credențială emisă din greșeală, o încălcare a politicii, o certificare expirată — iar un verificator corect trebuie să afișeze asta, nu doar să confirme că înregistrarea există.
  3. Se leagă tot lanțul? Referințele asertiunii către insignă și emitent se rezolvă la înregistrări reale, care se potrivesc, nu la ID-uri orfane sau nepotrivite.

Un widget de verificare care doar accesează un URL și caută statusul 200 ratează complet punctul 2. O asertiune revocată se rezolvă în continuare fără probleme — doar că poartă în conținut un indicator de revocare. Dacă widgetul tău verifică doar „a reușit cererea?”, o credențială revocată va apărea ca verificată, ceea ce e mai rău decât să nu ai niciun widget.

Pagina publică pentru oameni vs. endpointul API

Fiecare premiu emis pe https://badges.ninja primește un URL public pe care destinatarii îl pot distribui direct, fără să fie nevoie de cont pentru a-l vedea:

Public credential page

Pagina aceasta este cea către care vor trimite cei mai mulți destinatari de pe LinkedIn, dintr-un CV sau din semnătura de e-mail — este făcută pentru oameni, afișează imaginea insignei, emitentul și data emiterii și nu are nevoie de nimic din partea ta ca să funcționeze. Endpointul API de mai jos returnează aceeași asertiune, ca JSON-LD structurat, adică exact ce îți trebuie când îți construiești propria interfață în loc să trimiți doar spre a noastră.

Endpointul

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

Nu e nevoie de header de autentificare — endpointul este public în mod intenționat, pentru că tot rostul Open Badges este ca oricine (un angajator, un recruiter, un organism de licențiere) să poată verifica o credențială fără cont. Detaliile complete ale contractului, inclusiv numele exacte ale câmpurilor din asertiunea returnată, se găsesc în documentația de verificare.

Transmite GUID-ul premiului, pe care îl ai fie din propriile evidențe de emitere, fie extras dintr-un link badges.ninja/awards/<guid> primit de la un destinatar.

JS simplu: fetch și afișare

Acesta este întregul widget. Fără pas de build, fără pachet npm — îl lipești direct în orice pagină:

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

Înlocuiește REPLACE_WITH_AWARD_ID cu un GUID real de premiu. Asta e toată integrarea pe partea de client: fără graniță de iframe cu care să te lupți în CSS și fără script de la terți care să aducă propriile analytics sau pixeli de tracking odată cu verificarea.

Pe server: Node

Dacă preferi să verifici în backend — de exemplu înainte de a afișa o insignă „Partener verificat” pe o pagină de profil pe care o controlezi sau înainte de a accepta o credențială ca dovadă într-un flux de onboarding — fetch-ul este identic, fără partea de 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 };
}

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

Ambele exemple tratează un răspuns diferit de 200 ca „negăsit”, în loc să arunce o excepție. Un ID de premiu greșit sau inventat este o intrare așteptată pentru un endpoint public de verificare, nu una excepțională, așa că tratează-l ca pe o ramură obișnuită, nu ca pe o eroare prinsă.

Stilizează rezultatul în loc să integrezi interfața altcuiva

Fiindcă widgetul de mai sus este doar un apel fetch către propriul tău DOM, controlezi fiecare pixel. Un tipar frecvent are trei stări vizuale, mapate direct din răspuns:

  • Verificat — o bifă verde, textul „Verificat” și data emiterii, stilizate să se potrivească cu componentele de insignă/etichetă ale site-ului tău.
  • Revocat — o stare estompată sau roșie, care arată motivul revocării în loc să-l ascundă. Atât destinatarii, cât și terții beneficiază de un „de ce” sincer, nu de un eșec mut.
  • Negăsit — tratează-l vizual la fel ca „revocat” (nu afișa o stare falsă de succes), dar cu alt text, pentru că un ID greșit și o revocare deliberată sunt situații diferite pentru cine citește pagina.

Nimic din toate acestea nu cere o bibliotecă de componente. Un switch pe verified / revoked / not_found și trei clase CSS sunt suficiente.

Cache, ca să nu bombardezi endpointul

Endpointul de verificare este public și fără autentificare, ceea ce înseamnă și că nu există un cost în credite per cerere — dar tot înseamnă o călătorie prin rețea la fiecare încărcare de pagină dacă îl apelezi din client fără cache. Pentru o pagină care afișează aceeași insignă tuturor vizitatorilor (de exemplu o insignă „Partener verificat” în footer), păstrează rezultatul în cache pe server, cu un TTL scurt — între cinci și cincisprezece minute sunt mai mult decât suficiente, fiindcă revocările nu sunt un semnal care se schimbă rapid — în loc să repeți cererea la fiecare request:

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

Pentru un widget per vizitator (un destinatar care își integrează propria credențială pe un site personal), sari peste cache și apelează-l proaspăt la încărcarea paginii — cererea în plus e neglijabilă la scara aceea.

Ce oferă deja badges.ninja destinatarilor

Înainte să construiești un embed complet personalizat, merită să știi ce există deja din start. Pagina publică a fiecărui premiu include fragmente gata făcute de distribuire și embed, pe care destinatarii le pot copia direct, fără cod din partea lor:

Public share page with social buttons + embed snippets

Dacă scopul tău este „să las un destinatar să arate că insigna lui e reală”, un link către pagina lui badges.ninja/awards/<guid> este adesea mai simplu decât să construiești un widget propriu — este pagina pentru oameni de mai devreme și gestionează deja distincția verificat/revocat în locul tău. Construiește widgetul personalizat din acest articol atunci când ai nevoie ca starea verificării să fie afișată în interfața propriului produs — un director de parteneri, un panou intern de audit, o unealtă de recrutare care verifică certificările declarate ale unui candidat — acolo unde trimiterea către alt site nu e experiența potrivită.

De ce nu un iframe sau un widget de la un furnizor?

Unele platforme îți dau un fragment de genul <iframe src="https://vendor.example.com/verify-widget"> pentru a integra starea verificării. Funcționează, dar vine cu costuri reale: nu poți restiliza conținutul ca să se potrivească site-ului tău (rămâi cu CSS-ul livrat de furnizor), introduce un document cross-origin pe care Content-Security-Policy trebuie să-l permită explicit, iar fiecare încărcare de pagină face o cerere către gazda iframe-ului unui terț, indiferent dacă vizitatorul e interesat de starea verificării sau nu. Un widget cu fetch în două apeluri le evită pe toate trei — este DOM-ul tău, stilul tău și rulează doar când decizi tu să-l apelezi.

Parcurgerea întregului lanț de verificare

Dacă construiești ceva mai riguros decât un widget de stare — de pildă o unealtă de audit care verifică un lot de credențiale sau un raport de conformitate — parcurge tot lanțul, nu te opri la asertiune. Asertiunea face referire la un URL badge (BadgeClass: nume, descriere, criterii), iar BadgeClass face la rândul ei referire la un URL issuer (profilul organizației). Un verificator riguros le rezolvă pe toate trei și confirmă că sunt coerente între ele — insigna chiar aparține emitentului pe care îl declară, asertiunea chiar face referire la insigna pe care o declară — nu doar că asertiunea de la nivel superior returnează 200. E mai multă profunzime decât are nevoie un widget pentru o singură credențială, dar merită să știi că structura există, în caz că un partener sau un auditor te întreabă vreodată „cât de adâncă este de fapt verificarea voastră?”

Greșeli frecvente de evitat

  • Verificarea doar a statusului HTTP. Așa cum am arătat mai sus, o asertiune revocată se rezolvă în continuare. Citește mereu assertion.revoked, nu deduce verificarea doar din codul de status.
  • ID-ul premiului scris direct într-o componentă comună. Dacă widgetul trebuie să fie reutilizabil pe paginile mai multor destinatari, primește ID-ul premiului ca parametru (un atribut data, un query string, un prop), nu ca literal înglobat în script.
  • Înghițirea tăcută a erorilor de rețea. Un try/catch care nu loghează nimic și afișează același text „negăsit” ca pentru o insignă cu adevărat inexistentă face depanarea dureroasă mai târziu. Deosebește „apelul de rețea a eșuat” de „API-ul a spus nu” în ramurile de eroare, cel puțin în loguri.
  • Reverificarea la fiecare randare într-o aplicație client. Dacă widgetul trăiește într-o componentă React sau Vue care se re-randează des, verifică o singură dată per montare (sau o dată per fereastră de TTL din cache, conform secțiunii despre cache), nu la fiecare re-randare.

Costă credite și funcționează pe planul gratuit?

Nu. Endpointul de verificare este în mod deliberat în afara sistemului de credite care guvernează emiterea — a verifica dacă o insignă este validă nu costă nimic emitentul și nu cere ca vizitatorul care îl apelează să aibă cont, cheie API sau vreun plan. Asta e valabil indiferent dacă insigna a fost emisă pe planul Free, Starter sau Pro: verificarea este o proprietate a credențialei în sine, nu o funcție condiționată de abonamentul emitentului. De aceea poți pune în siguranță acest widget pe o pagină publică cu trafic imprevizibil (o pagină de cariere, un director de parteneri) fără grija limitelor de rată legate de contul tău — apelezi un endpoint public, doar pentru citire, nu o rută API autentificată.

Un lucru merită spus explicit: JSON-ul asertiunii returnat de endpoint nu include adresa de e-mail brută a destinatarului. E-mailul destinatarului este stocat sub formă de hash în backend tocmai pentru ca un răspuns public de verificare să nu poată fi folosit la extragerea de date de contact personale — ce primești este suficient pentru a confirma credențiala (insignă, emitent, date, stare de revocare), fără a transforma endpointul într-o unealtă de generare de lead-uri pentru oricine parcurge ID-uri de premii.

Gata să emiți prima ta credențială verificabilă?

Începe gratuit pe badges.ninja — designer vizual, pagină publică de verificare, certificat PDF, ieșire Open Badge v2.0. Nu e nevoie de card de credit.

Pentru structura completă și adnotată a fiecărui câmp dintr-o asertiune, vezi referința câmpurilor JSON-LD. Iar dacă emiți credențiale programatic, nu doar le verifici, ghidul rapid API acoperă autentificarea și apelul de creare a premiului din care provine rezultatul acestui endpoint.

Nacho Coll

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

Înapoi la Blog

Articole similare