Sematkan Verifikasi Lencana di Situs Web Anda (Tanpa Iframe, Tanpa SDK)

Tambahkan potongan JavaScript kecil (atau fetch di sisi server) ke situs Anda untuk memverifikasi Open Badge apa pun lewat endpoint JSON-LD publik. Tanpa iframe, tanpa pelacak pihak ketiga.

Nacho Coll Oleh Diperbarui 9 menit baca
Tambahkan potongan JavaScript kecil (atau fetch di sisi server) ke situs Anda untuk memverifikasi Open Badge apa pun lewat endpoint JSON-LD publik. Tanpa iframe, tanpa pelacak pihak ketiga.

Kalau Anda menerbitkan kredensial, cepat atau lambat ada yang bertanya: “Bagaimana saya tahu lencana ini asli?” Jawaban jujurnya tidak boleh “percaya saja pada PNG-nya.” Open Badge v2.0 menyelesaikannya dengan endpoint verifikasi publik yang bisa dibaca mesin, dan memasang widget “Verified” di situs Anda sendiri hanya butuh sekitar sepuluh baris kode — bukan iframe, bukan skrip embed pihak ketiga, dan bukan SDK.

Tulisan ini menunjukkan persis cara memanggil endpoint dan menampilkan hasilnya, dalam JavaScript murni serta di sisi server dengan Node dan Python. Kita juga membahas apa yang sebenarnya diperiksa oleh “verifikasi”, posisi endpoint ini dibanding halaman untuk manusia yang sudah disediakan badges.ninja bagi penerima, serta kesalahan yang membuat widget verifikasi tampak selesai padahal diam-diam tidak membuktikan apa-apa.

Apa arti sebenarnya dari “verified”

Assertion Open Badge v2.0 adalah dokumen JSON-LD dengan bentuk tetap: assertion itu sendiri (siapa penerimanya, kapan), referensi ke BadgeClass (apa lencana itu, siapa yang menentukan kriterianya), dan referensi ke profil penerbit (siapa yang menjamin). Saat Anda “memverifikasi” sebuah lencana, sebenarnya Anda memeriksa tiga hal terpisah:

  1. Apakah assertion ada dan dapat di-resolve. ID award-nya nyata, bukan tebakan atau hasil manipulasi.
  2. Apakah sudah dicabut. Penerbit dapat mencabut lencana setelah diterbitkan — kredensial yang terbit karena kesalahan, pelanggaran kebijakan, sertifikasi yang kedaluwarsa — dan verifier yang benar harus menampilkannya, bukan sekadar memastikan rekamannya ada.
  3. Apakah rantainya konsisten. Referensi badge dan penerbit pada assertion benar-benar mengarah ke rekaman nyata yang cocok, bukan ID yatim atau tidak sesuai.

Widget verifikasi yang hanya melakukan ping ke sebuah URL dan memeriksa status 200 melewatkan poin 2 sepenuhnya. Assertion yang dicabut tetap ter-resolve dengan baik — hanya saja membawa penanda pencabutan di payload-nya. Jika widget Anda cuma memeriksa “apakah request berhasil”, kredensial yang dicabut akan tampil sebagai terverifikasi, yang lebih buruk daripada tidak punya widget sama sekali.

Halaman publik untuk manusia vs. endpoint API

Setiap award yang diterbitkan di https://badges.ninja mendapat URL publik yang bisa langsung dibagikan penerima, tanpa perlu mendaftar untuk melihatnya:

Public credential page

Halaman itulah yang paling sering ditautkan penerima dari LinkedIn, resume, atau tanda tangan email — dibuat untuk manusia, menampilkan gambar lencana, penerbit, dan tanggal penerbitan, dan tidak butuh apa pun dari Anda agar berfungsi. Endpoint API di bawah mengembalikan assertion yang sama sebagai JSON-LD terstruktur, yang Anda perlukan saat membangun UI sendiri alih-alih sekadar menautkan ke situs kami.

Endpoint-nya

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

Tidak perlu header auth — ini sengaja dibuat publik, karena inti dari Open Badges adalah siapa pun (pemberi kerja, perekrut, badan perizinan) dapat memverifikasi kredensial tanpa akun. Detail kontrak lengkap, termasuk nama field persis pada assertion yang dikembalikan, ada di dokumentasi verifikasi.

Berikan GUID award, yang bisa Anda dapat dari catatan penerbitan sendiri atau diambil dari tautan badges.ninja/awards/<guid> yang dibagikan penerima kepada Anda.

JS murni: fetch dan tampilkan

Inilah seluruh widgetnya. Tanpa langkah build, tanpa paket npm, langsung tempel di halaman mana pun:

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

Ganti REPLACE_WITH_AWARD_ID dengan GUID award yang nyata. Itulah seluruh integrasi sisi klien: tanpa batas iframe yang harus dilawan dengan CSS, dan tanpa skrip pihak ketiga yang membawa analitik atau piksel pelacak sendiri di samping pemeriksaan verifikasi.

Sisi server: Node

Kalau Anda lebih suka memverifikasi di backend — misalnya sebelum menampilkan lencana “Verified Partner” di halaman profil yang Anda kendalikan, atau sebelum menerima kredensial sebagai bukti dalam alur onboarding — fetch-nya identik tanpa bagian 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 };
}

Sisi 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")}

Kedua contoh memperlakukan respons non-200 sebagai “not found” alih-alih melempar error. ID award yang rusak atau asal tebak adalah input yang wajar untuk endpoint verifikasi publik, bukan kondisi luar biasa, jadi tangani sebagai cabang normal, bukan error yang ditangkap.

Menata tampilan hasil, bukan menyematkan UI milik orang lain

Karena widget di atas hanyalah panggilan fetch ke DOM Anda sendiri, setiap piksel ada di tangan Anda. Pola umumnya adalah tiga status visual yang dipetakan langsung dari respons:

  • Verified — centang hijau plus “Verified” dan tanggal penerbitan, bergaya sesuai komponen badge/tag situs Anda.
  • Revoked — tampilan redup atau merah yang menunjukkan alasan pencabutan, bukan menyembunyikannya. Penerima dan pihak ketiga sama-sama diuntungkan oleh “mengapa” yang jujur, bukan kegagalan yang senyap.
  • Not found — perlakukan secara visual sama seperti “revoked” (jangan tampilkan status sukses palsu), tetapi dengan teks berbeda, karena ID yang salah dan pencabutan yang disengaja adalah situasi yang berbeda bagi pembaca halaman.

Semua ini tidak butuh pustaka komponen. Sebuah switch pada verified / revoked / not_found dan tiga kelas CSS sudah cukup.

Caching, agar endpoint tidak dihujani request

Endpoint verifikasi bersifat publik dan tanpa autentikasi, artinya tidak ada biaya kredit per request — tetapi tetap saja satu round trip jaringan di setiap pemuatan halaman jika dipanggil di sisi klien tanpa caching. Untuk halaman yang menampilkan lencana yang sama kepada setiap pengunjung (misalnya lencana “Verified Partner” di footer), cache hasilnya di sisi server dengan TTL singkat — lima sampai lima belas menit sudah cukup, karena pencabutan bukan sinyal yang cepat berubah — daripada fetch ulang pada setiap 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;
}

Untuk widget per pengunjung (penerima yang menyematkan kredensialnya sendiri di situs pribadi), lewati cache dan panggil langsung saat halaman dimuat — satu request tambahan tidak berarti pada skala itu.

Apa yang sudah diberikan badges.ninja kepada penerima

Sebelum membangun embed yang sepenuhnya kustom, ada baiknya tahu apa yang sudah tersedia. Halaman publik setiap award sudah menyertakan cuplikan bagikan dan embed siap pakai yang bisa disalin penerima langsung, tanpa kode di sisi mereka:

Public share page with social buttons + embed snippets

Jika kebutuhan Anda adalah “biarkan penerima menunjukkan bahwa lencananya asli,” menautkan ke halaman badges.ninja/awards/<guid> miliknya sering kali lebih sederhana daripada membangun widget kustom — itu halaman untuk manusia yang ditampilkan tadi, dan sudah menangani pembedaan verified/revoked untuk Anda. Bangun widget kustom dari tulisan ini ketika Anda perlu menampilkan status verifikasi di dalam UI produk Anda sendiri — direktori mitra, dasbor audit internal, alat rekrutmen yang memeriksa sertifikasi klaim kandidat — di mana mengarahkan ke situs lain bukan UX yang tepat.

Mengapa bukan iframe atau widget vendor?

Sebagian platform memberi Anda cuplikan seperti <iframe src="https://vendor.example.com/verify-widget"> untuk menyematkan status verifikasi. Memang berfungsi, tetapi ada biaya nyata: Anda tidak bisa menata ulang isinya agar sesuai situs (terpaksa memakai CSS bawaan vendor), ia menambah dokumen lintas origin yang harus diizinkan secara eksplisit oleh Content-Security-Policy Anda, dan setiap pemuatan halaman mengirim request ke host iframe pihak ketiga entah pengunjung peduli pada status verifikasi atau tidak. Widget fetch dua panggilan menghindari ketiganya — DOM milik Anda, gaya milik Anda, dan hanya berjalan saat Anda memutuskan memanggilnya.

Menelusuri seluruh rantai verifikasi

Jika Anda membangun sesuatu yang lebih menyeluruh daripada widget status — misalnya alat audit yang memeriksa sekumpulan kredensial, atau laporan kepatuhan — telusuri seluruh rantai, jangan berhenti di assertion. Assertion merujuk ke URL badge (BadgeClass: nama, deskripsi, kriteria), dan BadgeClass merujuk ke URL issuer (profil organisasi). Verifier yang ketat me-resolve ketiganya dan memastikan semuanya konsisten — lencana benar-benar milik penerbit yang diklaim, assertion benar-benar merujuk ke lencana yang diklaim — bukan sekadar assertion tingkat atas mengembalikan 200. Itu lebih dalam dari kebutuhan widget status satu kredensial, tetapi bagus untuk diketahui jika suatu saat mitra atau auditor bertanya, “seberapa dalam sebenarnya verifikasi Anda?”

Kesalahan umum yang perlu dihindari

  • Hanya memeriksa status HTTP. Seperti dibahas di atas, assertion yang dicabut tetap ter-resolve. Selalu baca assertion.revoked, jangan menyimpulkan verifikasi dari kode status saja.
  • Meng-hardcode ID award di komponen bersama. Jika widget dimaksudkan dapat dipakai ulang di halaman banyak penerima, jadikan ID award sebagai parameter (data attribute, query string, prop), bukan literal yang tertanam di skrip.
  • Menelan error jaringan secara diam-diam. try/catch yang tidak mencatat apa pun dan menampilkan teks “not found” yang sama dengan lencana yang benar-benar tidak ada membuat debugging menyakitkan nanti. Bedakan “panggilan jaringan gagal” dari “API menjawab tidak” di cabang error Anda, setidaknya di log.
  • Memverifikasi ulang di setiap render pada aplikasi sisi klien. Jika widget berada di dalam komponen React atau Vue yang sering me-render ulang, verifikasi sekali per mount (atau sekali per jendela TTL cache, sesuai bagian caching di atas), bukan di setiap render ulang.

Apakah ini memakai kredit, dan apakah berjalan di paket gratis?

Tidak. Endpoint verifikasi sengaja berada di luar sistem kredit yang mengatur penerbitan — memeriksa apakah sebuah lencana valid tidak membebani penerbit, dan pengunjung yang memanggilnya tidak perlu akun, API key, atau tingkat paket apa pun. Ini berlaku baik lencana diterbitkan di paket Free, Starter, maupun Pro: verifikasi adalah properti kredensial itu sendiri, bukan fitur yang dibatasi langganan penerbit. Itu juga alasan Anda aman menaruh widget ini di halaman publik dengan trafik tak terduga (halaman karier, direktori mitra) tanpa khawatir soal rate limit yang terkait akun Anda — Anda memanggil endpoint publik read-only, bukan rute API terautentikasi.

Satu hal yang perlu ditegaskan: JSON assertion yang dikembalikan endpoint tidak menyertakan alamat email mentah penerima. Email penerima disimpan dalam bentuk hash di backend justru agar respons verifikasi publik tidak bisa di-scrape untuk mendapatkan informasi kontak pribadi — yang Anda terima cukup untuk mengonfirmasi kredensial (lencana, penerbit, tanggal, status pencabutan) tanpa menjadikan endpoint ini alat lead generation bagi siapa pun yang mengiterasi ID award.

Siap menerbitkan kredensial terverifikasi pertama Anda?

Mulai gratis di badges.ninja — desainer visual, halaman verifikasi publik, sertifikat PDF, output Open Badge v2.0. Tanpa kartu kredit.

Untuk struktur beranotasi lengkap setiap field dalam assertion, lihat referensi field JSON-LD. Dan jika Anda menerbitkan kredensial secara terprogram, bukan sekadar memverifikasinya, panduan cepat API membahas autentikasi dan pemanggilan pembuatan award yang menghasilkan output endpoint ini.

Nacho Coll

Tentang penulis

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.

Kembali ke Blog

Artikel Terkait