Вбудуйте верифікацію значка на свій сайт (без iframe, без SDK)
Додайте на свій сайт невеликий фрагмент JavaScript (або серверний запит), який верифікує будь-який Open Badge через публічний ендпоінт JSON-LD. Без iframe, без стороннього трекера.
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.
Якщо ви видаєте цифрові посвідчення, рано чи пізно хтось запитає: «як мені дізнатися, що цей значок справжній?». Чесна відповідь ніколи не має звучати як «довіряйте PNG-файлу». Open Badge v2.0 вирішує це за допомогою публічного машинозчитуваного ендпоінта верифікації, а віджет «Верифіковано» на вашому сайті — це приблизно десять рядків коду: без iframe, без стороннього скрипта для вбудовування й без SDK.
Ця стаття показує, як саме викликати ендпоінт і відобразити результат — на чистому JavaScript, а також на сервері в Node і Python. Вона також пояснює, що насправді перевіряє «верифікація», як ендпоінт співвідноситься зі сторінками для людей, які badges.ninja уже надає отримувачам, і які помилки роблять віджет верифікації схожим на завершений, хоча він тихо нічого не доводить.
Що насправді означає «верифіковано»
Твердження (assertion) Open Badge v2.0 — це документ JSON-LD із фіксованою структурою: саме твердження (хто його отримав і коли), посилання на BadgeClass (що це за значок і хто визначає критерії) та посилання на профіль емітента (хто за нього ручається). Коли ви «верифікуєте» значок, насправді ви перевіряєте три окремі речі:
- Чи існує твердження і чи воно відкривається. Ідентифікатор нагороди справжній, а не вгаданий чи підроблений.
- Чи його відкликано. Емітент може відкликати значок постфактум — посвідчення видано помилково, порушено правила, термін сертифікації минув — і коректний верифікатор має це показати, а не лише підтвердити, що запис існує.
- Чи узгоджується весь ланцюжок. Посилання твердження на значок і емітента справді ведуть до реальних записів, що збігаються, а не до «осиротілих» чи невідповідних ідентифікаторів.
Віджет, який лише звертається до URL і перевіряє статус 200, повністю пропускає пункт 2. Відкликане твердження все одно успішно відкривається — просто в даних має прапорець відкликання. Якщо ваш віджет перевіряє лише «чи запит успішний», відкликане посвідчення відобразиться як верифіковане, а це гірше, ніж узагалі не мати віджета.
Публічна сторінка для людей і ендпоінт API
Кожна нагорода, видана на https://badges.ninja, отримує публічну URL-адресу, якою отримувачі можуть ділитися напряму, а для її перегляду реєстрація не потрібна:

Саме на цю сторінку більшість отримувачів посилатимуться з LinkedIn, резюме чи підпису в листі — вона створена для людей, показує зображення значка, емітента й дату видачі та не потребує від вас жодних дій. Ендпоінт API нижче повертає те саме твердження у вигляді структурованого JSON-LD — це потрібно, коли ви будуєте власний інтерфейс, а не просто посилаєтеся на наш.
Ендпоінт
GET https://api.badges.ninja/certify-badge/award/{guid}
Заголовок авторизації не потрібен — ендпоінт навмисно публічний, адже вся суть Open Badges у тому, що будь-хто (роботодавець, рекрутер, ліцензійна комісія) може верифікувати посвідчення без облікового запису. Повну специфікацію, зокрема точні назви полів у поверненому твердженні, наведено в документації з верифікації.
Передайте GUID нагороди, який ви маєте або зі своїх записів про видачу, або можете взяти з посилання badges.ninja/awards/<guid>, яким поділився отримувач.
Чистий JS: запит і відображення
Це весь віджет. Без етапу збірки, без npm-пакета — просто вставте на будь-яку сторінку:
<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>
Замініть REPLACE_WITH_AWARD_ID на справжній GUID нагороди. Це вся інтеграція на стороні клієнта: жодної межі iframe, з якою довелося б боротися в CSS, і жодного стороннього скрипта, що тягне власну аналітику чи піксели відстеження разом із перевіркою верифікації.
На сервері: Node
Якщо ви волієте верифікувати на бекенді — скажімо, перед показом значка «Верифікований партнер» на сторінці профілю, яку ви контролюєте, або перед тим, як прийняти посвідчення як доказ в онбордингу, — запит той самий, лише без частини з 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 };
}
На сервері: 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")}
Обидва приклади трактують відповідь, відмінну від 200, як «не знайдено», а не кидають виняток. Зламаний або вгаданий ідентифікатор нагороди — це очікуваний ввід для публічного ендпоінта верифікації, а не виняткова ситуація, тому обробляйте його як звичайну гілку, а не як перехоплену помилку.
Стилізуйте результат замість чужого інтерфейсу
Оскільки віджет вище — це просто виклик fetch, що пише у ваш власний DOM, кожен піксель належить вам. Поширений підхід — три візуальні стани, що напряму відповідають відповіді:
- Верифіковано — зелена галочка, напис «Верифіковано» й дата видачі, у стилі компонентів значків/тегів вашого сайту.
- Відкликано — приглушений або червоний стан, що показує причину відкликання, а не приховує її. Чесне «чому» корисне і отримувачам, і третім особам, на відміну від мовчазної помилки.
- Не знайдено — візуально трактуйте це так само, як «відкликано» (не показуйте хибний стан успіху), але з іншим текстом, адже хибний ідентифікатор і навмисне відкликання — різні ситуації для того, хто читає сторінку.
Для цього не потрібна бібліотека компонентів. Вистачить switch за verified / revoked / not_found і трьох класів CSS.
Кешування, щоб не навантажувати ендпоінт
Ендпоінт верифікації публічний і не потребує автентифікації, а отже, немає й вартості кредитів за запит — проте це все одно мережевий виклик при кожному завантаженні сторінки, якщо викликати його на клієнті без кешування. Для сторінки, що показує той самий значок усім відвідувачам (наприклад, значок «Верифікований партнер» у футері), кешуйте результат на сервері з коротким TTL — п’яти–п’ятнадцяти хвилин цілком достатньо, бо відкликання — не швидкозмінний сигнал, — замість того щоб робити запит щоразу:
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;
}
Для віджета «на кожного відвідувача» (отримувач вбудовує власне посвідчення на персональному сайті) пропустіть кеш і просто викликайте ендпоінт при завантаженні сторінки — додатковий запит у такому масштабі непомітний.
Що badges.ninja уже дає отримувачам
Перш ніж будувати повністю власне вбудовування, варто знати, що вже доступно «з коробки». Публічна сторінка кожної нагороди містить готові фрагменти для поширення та вбудовування, які отримувачі можуть скопіювати без жодного коду:

Якщо ваш сценарій — «дати отримувачу змогу показати, що його значок справжній», то посилання на його сторінку badges.ninja/awards/<guid> часто простіше, ніж будувати власний віджет, — це описана вище сторінка для людей, і вона вже сама розрізняє верифіковані та відкликані значки. Власний віджет із цієї статті будуйте тоді, коли статус верифікації потрібно показати всередині інтерфейсу вашого продукту — у каталозі партнерів, внутрішній панелі аудиту, інструменті найму, що перевіряє заявлені сертифікації кандидата, — де перенаправлення на інший сайт не є доречним UX.
Чому не iframe чи віджет від постачальника?
Деякі платформи дають фрагмент на кшталт <iframe src="https://vendor.example.com/verify-widget"> для вбудовування статусу верифікації. Це працює, але має реальну ціну: ви не можете змінити вигляд вмісту під свій сайт (залишаєтеся з CSS, який поставив постачальник), це додає міждоменний документ, який ваша Content-Security-Policy має явно дозволити, а кожне завантаження сторінки надсилає запит до хоста iframe третьої сторони незалежно від того, чи цікавить відвідувача статус верифікації. Віджет на одному виклику fetch уникає всіх трьох проблем — це ваш DOM, ваші стилі, і він працює лише тоді, коли ви вирішите його викликати.
Проходження всього ланцюжка верифікації
Якщо ви будуєте щось ґрунтовніше за віджет статусу — наприклад, інструмент аудиту, що перевіряє пакет посвідчень, або звіт про відповідність, — проходьте весь ланцюжок, а не зупиняйтеся на твердженні. Твердження посилається на URL badge (BadgeClass: назва, опис, критерії), а BadgeClass своєю чергою — на URL issuer (профіль організації). Ретельний верифікатор розв’язує всі три й підтверджує їхню внутрішню узгодженість — значок справді належить емітентові, якого вказує, а твердження справді посилається на значок, який вказує, — а не лише те, що твердження верхнього рівня повертає 200. Це глибше, ніж потрібно віджету статусу окремого посвідчення, але добре знати про цю структуру на випадок, якщо партнер чи аудитор запитає: «наскільки глибока ваша верифікація насправді?».
Типові помилки, яких слід уникати
- Перевірка лише HTTP-статусу. Як сказано вище, відкликане твердження все одно відкривається. Завжди читайте
assertion.revokedі не робіть висновок про верифікацію лише за кодом статусу. - Жорстко прописаний ідентифікатор нагороди у спільному компоненті. Якщо віджет має бути багаторазовим на сторінках багатьох отримувачів, передавайте ідентифікатор нагороди як параметр (data-атрибут, рядок запиту, prop), а не літерал, зашитий у скрипті.
- Мовчазне ігнорування мережевих помилок.
try/catch, який нічого не логує й показує той самий текст «не знайдено», що й для справді відсутнього значка, ускладнює подальше налагодження. Розрізняйте «мережевий виклик не вдався» та «API відповіло відмовою» у гілках помилок, принаймні в логах. - Повторна верифікація при кожному рендері в клієнтському застосунку. Якщо віджет живе всередині компонента React чи Vue, що часто перерендерюється, верифікуйте один раз на монтування (або один раз на вікно TTL кешу, як у розділі про кешування), а не при кожному повторному рендері.
Чи це коштує кредитів і чи працює на безкоштовному плані?
Ні. Ендпоінт верифікації навмисно винесено за межі системи кредитів, що регулює видачу, — перевірка того, чи дійсний значок, нічого не коштує емітентові й не вимагає від відвідувача, який його викликає, облікового запису, API-ключа чи будь-якого плану. Це так незалежно від того, чи значок видано на плані Free, Starter чи Pro: верифікація — властивість самого посвідчення, а не функція, закрита підпискою емітента. Тому ви можете спокійно ставити цей віджет на публічну сторінку з непередбачуваним трафіком (сторінка вакансій, каталог партнерів), не переживаючи про ліміти, пов’язані з вашим обліковим записом, — ви звертаєтеся до публічного ендпоінта лише для читання, а не до автентифікованого маршруту API.
Одне варто зазначити окремо: JSON твердження, що повертає ендпоінт, не містить необробленої електронної адреси отримувача. Електронну адресу отримувача на бекенді зберігають у вигляді хеша саме для того, щоб із публічної відповіді верифікації не можна було зібрати контактні дані, — отриманого достатньо, щоб підтвердити посвідчення (значок, емітент, дати, статус відкликання), не перетворюючи ендпоінт на інструмент збору лідів для будь-кого, хто перебирає ідентифікатори нагород.
Готові видати своє перше верифіковане посвідчення?
Почніть безкоштовно на badges.ninja — візуальний конструктор, публічна сторінка верифікації, PDF-сертифікат, вихідний формат Open Badge v2.0. Без кредитної картки.
Повну анотовану структуру кожного поля твердження дивіться в довіднику полів JSON-LD. А якщо ви видаєте посвідчення програмно, а не лише верифікуєте їх, короткий посібник з API описує автентифікацію та виклик створення нагороди, з якого походять дані цього ендпоінта.
Як створено цю статтю
Деякі публікації в цьому блозі створюються за допомогою ШІ-асистента, а потім перевіряються, звіряються з фактами й редагуються командою Badges Ninja перед публікацією. Кожен приклад коду та ціна звіряються з актуальним продуктом. Докладніше про наш редакційний процес і процес роботи з ШІ читайте на сторінці редакційного процесу .

Про автора
Nacho Coll
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.

