Salesforce Open Badges: выдача верифицируемых credentials по триггерам CRM

Запускайте выдачу Open Badge по любому событию объекта Salesforce — лид выигран, аккаунт повышен, пройден этап обучения — с помощью Apex-вызова API Badges Ninja.

Nacho Coll Автор Обновлено 8 мин чтения
Запускайте выдачу Open Badge по любому событию объекта Salesforce — лид выигран, аккаунт повышен, пройден этап обучения — с помощью Apex-вызова API Badges Ninja.

Большинство команд по обучению уже фиксируют в 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.

Что вы будете строить

Схема состоит из четырёх частей:

  1. Значок, созданный в визуальном редакторе. Скопируйте его идентификатор (поле id, которое возвращает GET /badges).
  2. API-ключ, созданный на странице API Keys вашего дашборда. Ключ показывается один раз, поэтому сразу сохраните его.
  3. Named Credential в Salesforce, в котором хранится ключ, чтобы он никогда не попадал в код.
  4. Apex-триггер и класс Queueable, которые выдают награду и записывают ссылку на неё обратно в запись.

Только что созданный API-ключ, показанный один раз

Шаг 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-ключей с маскированным префиксом

Обработка ошибок и повторные попытки

API возвращает стандартные коды статуса, и на каждый из них вашему коду нужно реагировать по-своему:

СтатусЗначениеЧто делать
400Поле отсутствовало или было некорректным, в том числе имя получателя короче пяти символовНе повторять. Исправить данные и записать сообщение в Badge_Error__c.
401Неверный или отсутствующий API-ключПроверить Named Credential. Повтор не поможет, пока это не исправлено.
402Закончились credentials или квота вашего тарифаОстановить выдачу и оповестить администратора; повторить после повышения тарифа или сброса квоты.
404badgeId не существуетИсправить 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 сопоставляет цены и возможности бок о бок.

Тестирование перед запуском

Сначала прогоните весь процесс в песочнице.

  1. Создайте тестовый значок и тестового получателя с адресом электронной почты, к которому у вас есть доступ.
  2. В тестах Apex реализуйте HttpCalloutMock, возвращающий статус 200 и {"awardId":"test-award-id"}; Salesforce не разрешает реальные callouts в тестовом контексте, а это покрытие нужно для развёртывания.
  3. В песочнице переведите одно зачисление в Completed и убедитесь, что Badge_Award_Id__c и Badge_Url__c заполняются в течение минуты.
  4. Откройте URL в приватном окне браузера и убедитесь, что публичная страница верификации отображается без входа.
  5. Спровоцируйте сбой (очистите идентификатор значка или используйте имя получателя короче пяти символов) и убедитесь, что ошибка попадает в Badge_Error__c.

Смените API-ключ, если он когда-либо попадёт в журнал отладки или на скриншот. Отзыв происходит мгновенно на странице API Keys, а обновить нужно лишь External Credential, а не ваш код.

Итоги

Шаблон невелик: тонкий триггер решает, когда; Queueable выполняет вызов; Named Credential хранит ключ; три поля в записи сохраняют результат. Он подходит для этапов обучения, партнёрских уровней, посещения мероприятий и любого изменения состояния, которое можно выразить в Salesforce.

Начните с одного объекта и одного значка, понаблюдайте за ним неделю и расширяйте решение, когда поведение повторных попыток будет вас устраивать.

Готовы выдать первый верифицируемый credential? Начните бесплатно на badges.ninja — визуальный редактор, публичная страница верификации, PDF-сертификат, вывод в формате Open Badge v2.0. Банковская карта не требуется.

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.

Назад в блог

Похожие статьи