Salesforce Open Badges: выдача верифицируемых credentials по триггерам CRM
Запускайте выдачу Open Badge по любому событию объекта Salesforce — лид выигран, аккаунт повышен, пройден этап обучения — с помощью Apex-вызова API 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.
Большинство команд по обучению уже фиксируют в Salesforce, кто что завершил. Запись о зачислении на курс переходит в статус «Completed», партнёрский аккаунт достигает уровня сертификации, клиент проходит онбординг. Запись меняется, а через неделю кто-то выгружает список и вручную рассылает сертификаты по почте.
В этом руководстве показано, как закрыть этот разрыв: когда запись достигает нужного вам статуса, Salesforce вызывает API https://badges.ninja, выдаётся credential Open Badge v2.0, а ссылка для верификации сохраняется в этой же записи. Затем вы можете вставить эту ссылку в шаблон письма, отчёт или страницу Experience Cloud.
Вам понадобятся администратор Salesforce (или песочница разработчика), уже созданный на https://badges.ninja значок и API-ключ. Если вы ещё ни разу не обращались к API, сначала прочитайте краткое руководство по API; оно займёт десять минут.
Когда триггер CRM — подходящий инструмент
Прежде чем писать Apex, проверьте, нужен ли он вам. Если вы выдаёте credentials пакетами раз в семестр или раз в квартал, загрузка CSV в дашборде быстрее и вообще не требует кода. Путь через Salesforce оправдан, когда:
- Выдача должна происходить в момент изменения записи, без участия человека.
- Данные о том, «кто это заслужил», уже хранятся в Salesforce, и вы не хотите второго источника истины.
- Вы хотите хранить ссылку на credential в записи Contact, Lead или в пользовательской записи обучения для отчётности.
Если ваш инструмент рабочих процессов — Zapier, а не Apex, тот же вызов API работает и там; см. автоматизацию Open Badges через Zapier.
Что вы будете строить
Схема состоит из четырёх частей:
- Значок, созданный в визуальном редакторе. Скопируйте его идентификатор (поле
id, которое возвращаетGET /badges). - API-ключ, созданный на странице API Keys вашего дашборда. Ключ показывается один раз, поэтому сразу сохраните его.
- Named Credential в Salesforce, в котором хранится ключ, чтобы он никогда не попадал в код.
- Apex-триггер и класс Queueable, которые выдают награду и записывают ссылку на неё обратно в запись.

Шаг 1: храним ключ в Named Credential
Жёстко прописывать API-ключ в Apex — ошибка, которой стоит избегать. В Setup создайте External Credential с пользовательским заголовком, а затем Named Credential, указывающий на хост API:
- URL:
https://api.badges.ninja - Имя пользовательского заголовка:
X-Api-Key - Значение пользовательского заголовка: ваш ключ в формате
bws_YOUR_KEY_HERE - Имя Named Credential:
BadgesNinja
Предоставьте набору разрешений, от имени которого работает ваша автоматизация, доступ к принципалу External Credential. При наличии Named Credential отдельная Remote Site Setting не нужна: Salesforce авторизует вызов через credential. Если ваша организация всё ещё использует устаревшие Named Credentials или обычный endpoint, добавьте https://api.badges.ninja как Remote Site Setting и отправляйте заголовок из кода.
Шаг 2: добавляем поля в запись
В объект, который представляет достижение (мы будем использовать пользовательский объект Training_Enrollment__c), добавьте:
Badge_Id__c(Text): значок, который нужно выдать. Если хранить его для каждого курса, один триггер обслуживает множество значков.Badge_Award_Id__c(Text, уникальное): идентификатор награды, возвращённый API.Badge_Url__c(URL): публичная страница верификации.Badge_Error__c(Long Text): последняя ошибка, чтобы администраторы видели сбои, не открывая журналы отладки.
Поле Badge_Award_Id__c выполняет сразу две функции. Это ваша защита от дублей: триггер выдаёт значок, только если оно пустое.
Шаг 3: вызов Apex
Триггер не может напрямую выполнить callout, поскольку callouts не разрешены в одной транзакции с незафиксированным DML. Передайте работу классу Queueable, реализующему Database.AllowsCallouts.
Контракт API для POST /awards краток. Тело содержит badgeId, вложенный объект recipient с полями name и email, а также обязательное поле issuedOn. Успешный вызов возвращает HTTP 200 и { "awardId": "..." }. Имя получателя должно содержать не менее пяти символов, поэтому контакт с именем «Al Li» будет отклонён; проверяйте данные до вызова.
public class BadgeIssueJob implements Queueable, Database.AllowsCallouts {
private Id enrollmentId;
public BadgeIssueJob(Id enrollmentId) { this.enrollmentId = enrollmentId; }
public void execute(QueueableContext ctx) {
Training_Enrollment__c e = [
SELECT Id, Badge_Id__c, Badge_Award_Id__c, Contact__r.Name, Contact__r.Email
FROM Training_Enrollment__c WHERE Id = :enrollmentId LIMIT 1
];
if (e.Badge_Award_Id__c != null) { return; } // already issued
Map<String, Object> recipient = new Map<String, Object>{
'name' => e.Contact__r.Name,
'email' => e.Contact__r.Email
};
Map<String, Object> body = new Map<String, Object>{
'badgeId' => e.Badge_Id__c,
'recipient' => recipient,
'issuedOn' => Datetime.now().getTime()
};
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:BadgesNinja/awards');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(body));
HttpResponse res = new Http().send(req);
if (res.getStatusCode() == 200) {
Map<String, Object> out = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
String awardId = (String) out.get('awardId');
e.Badge_Award_Id__c = awardId;
e.Badge_Url__c = 'https://badges.ninja/awards/' + awardId;
e.Badge_Error__c = null;
} else {
e.Badge_Error__c = res.getStatusCode() + ': ' + res.getBody();
}
update e;
}
}
Стоит обратить внимание на две детали. Во-первых, issuedOn — это миллисекунды эпохи, ровно то, что возвращает Datetime.getTime(). Во-вторых, ответ не содержит URL верификации, поэтому код собирает его из идентификатора награды: https://badges.ninja/awards/<awardId>.
Шаг 4: триггер
Делайте триггер тонким. Он лишь решает, какие записи подходят, и ставит в очередь по одной задаче на запись.
trigger TrainingEnrollmentBadge on Training_Enrollment__c (after insert, after update) {
for (Training_Enrollment__c e : Trigger.new) {
Boolean completed = e.Status__c == 'Completed';
Boolean wasCompleted = Trigger.isUpdate && Trigger.oldMap.get(e.Id).Status__c == 'Completed';
if (completed && !wasCompleted && e.Badge_Award_Id__c == null && e.Badge_Id__c != null) {
System.enqueueJob(new BadgeIssueJob(e.Id));
}
}
}
Проверка wasCompleted важна. Без неё любая последующая правка завершённой записи снова запускала бы эту логику. Вместе с защитой по пустому идентификатору награды внутри задачи это гарантирует, что одна запись породит ровно один credential.
Один лимит необходимо учитывать: Salesforce разрешает поставить в очередь из одной транзакции лишь небольшое число Queueable-задач (50 в синхронном контексте). Если вы загружаете завершения массово, например при импорте 2 000 строк, приведённый выше триггер упрётся в этот потолок. Для массовых загрузок либо загрузите список через массовую выдачу наград из CSV в дашборде, либо перейдите на класс Batchable с Database.AllowsCallouts, который обрабатывает в каждом scope лишь несколько записей.

Обработка ошибок и повторные попытки
API возвращает стандартные коды статуса, и на каждый из них вашему коду нужно реагировать по-своему:
| Статус | Значение | Что делать |
|---|---|---|
| 400 | Поле отсутствовало или было некорректным, в том числе имя получателя короче пяти символов | Не повторять. Исправить данные и записать сообщение в Badge_Error__c. |
| 401 | Неверный или отсутствующий API-ключ | Проверить Named Credential. Повтор не поможет, пока это не исправлено. |
| 402 | Закончились credentials или квота вашего тарифа | Остановить выдачу и оповестить администратора; повторить после повышения тарифа или сброса квоты. |
| 404 | badgeId не существует | Исправить Badge_Id__c в записи или курсе. |
| 5xx или тайм-аут | Временный сбой | Можно безопасно повторить позже. |
Поскольку задача записывает ошибку в запись и оставляет Badge_Award_Id__c пустым, повтор сводится к переключению флажка или повторному сохранению записи. Небольшой запланированный класс Apex, который ищет завершённые зачисления без идентификатора награды и с временной ошибкой и снова ставит их в очередь, даёт вам автоматическую очередь повторов без дополнительной инфраструктуры. Ограничьте число попыток полем-счётчиком, чтобы заведомо неисправная запись не зацикливалась.
Проверьте свой тариф, прежде чем подключать это к продакшену. Тариф Free включает 1 200 credentials в год; Starter стоит $9 в месяц, а Pro — $29 в месяц. Триггер, срабатывающий на каждый Lead, быстро исчерпает небольшой тариф, поэтому ограничивайте критерии реальными достижениями.
Делаем credential видимым в Salesforce
Когда Badge_Url__c заполнено, его можно использовать где угодно:
- Email-оповещения и шаблоны: подставьте поле в письмо «Поздравляем, вот ваш credential».
- Отчёты: отчёт по условию
Badge_Award_Id__c != nullпокажет количество выдач по курсам, регионам или владельцам аккаунтов. - Experience Cloud: покажите ссылку на странице сертификации партнёра, чтобы покупатели могли проверить её сами.
- Макеты страниц: добавьте поле URL, чтобы сотрудники поддержки могли одним взглядом ответить на вопрос «получила ли она свой значок?».
Страница верификации публична и не требует регистрации — в этом и смысл. Получатель, менеджер по найму или отдел закупок клиента может открыть ссылку и убедиться, кто выдал credential, что он подтверждает и когда. Для машиночитаемых проверок JSON-LD Open Badge v2.0 любой награды доступен по адресу https://api.badges.ninja/certify-badge/award/<awardId>.
Получатели также получают привычные дополнения: уникальный URL верификации, QR-код, PDF-сертификат формата A4 и опцию «Добавить в LinkedIn» в портале получателя, вход в который выполняется по magic link, а не по паролю.
Почему это лучше плагина значков из AppExchange
Плагины значков в AppExchange, как правило, тарифицируются за место в Salesforce или за лицензию управляемого пакета. Эта стоимость растёт с размером вашей команды в Salesforce, а не с числом выданных credentials, так что команда обучения из десяти человек и программа с десятью тысячами учащихся платят за один и тот же результат очень по-разному.
API-интеграция переворачивает эту модель. Вы платите за объём credentials по фиксированному тарифу, код триггера принадлежит вам — его можно читать и менять, а результат — стандартный Open Badge v2.0, поэтому получатели не привязаны к проприетарному просмотрщику. Вы также сохраняете свободу дизайна: визуальный редактор предлагает 80+ шаблонов, форм, иконок и собственные загрузки вместо фиксированного макета внутри пакета.
Честный компромисс: плагин ставится одним кликом и не требует Apex. Если в вашей команде некому поддерживать несколько десятков строк кода, плагин или no-code-коннектор может подойти лучше. Если вы сравниваете платформы в целом, а не интеграции, обзор альтернативы Credly сопоставляет цены и возможности бок о бок.
Тестирование перед запуском
Сначала прогоните весь процесс в песочнице.
- Создайте тестовый значок и тестового получателя с адресом электронной почты, к которому у вас есть доступ.
- В тестах Apex реализуйте
HttpCalloutMock, возвращающий статус 200 и{"awardId":"test-award-id"}; Salesforce не разрешает реальные callouts в тестовом контексте, а это покрытие нужно для развёртывания. - В песочнице переведите одно зачисление в Completed и убедитесь, что
Badge_Award_Id__cиBadge_Url__cзаполняются в течение минуты. - Откройте URL в приватном окне браузера и убедитесь, что публичная страница верификации отображается без входа.
- Спровоцируйте сбой (очистите идентификатор значка или используйте имя получателя короче пяти символов) и убедитесь, что ошибка попадает в
Badge_Error__c.
Смените API-ключ, если он когда-либо попадёт в журнал отладки или на скриншот. Отзыв происходит мгновенно на странице API Keys, а обновить нужно лишь External Credential, а не ваш код.
Итоги
Шаблон невелик: тонкий триггер решает, когда; Queueable выполняет вызов; Named Credential хранит ключ; три поля в записи сохраняют результат. Он подходит для этапов обучения, партнёрских уровней, посещения мероприятий и любого изменения состояния, которое можно выразить в Salesforce.
Начните с одного объекта и одного значка, понаблюдайте за ним неделю и расширяйте решение, когда поведение повторных попыток будет вас устраивать.
Готовы выдать первый верифицируемый credential? Начните бесплатно на badges.ninja — визуальный редактор, публичная страница верификации, PDF-сертификат, вывод в формате Open Badge v2.0. Банковская карта не требуется.
Как создана эта статья
Некоторые статьи в этом блоге готовятся с помощью ИИ-ассистента, а затем проверяются, сверяются с фактами и редактируются командой 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.

