Salesforce と Open Badges:CRM のトリガーで検証可能なクレデンシャルを発行する

Lead の成約、Account のアップグレード、研修のマイルストーンなど、Salesforce のあらゆるオブジェクトのイベントから、Apex コールアウトで Badges Ninja API を呼び出し、Open Badge を発行できます。

Nacho Coll 著者: 更新日 17 分で読了
Lead の成約、Account のアップグレード、研修のマイルストーンなど、Salesforce のあらゆるオブジェクトのイベントから、Apex コールアウトで Badges Ninja API を呼び出し、Open Badge を発行できます。

多くの研修チームは、誰が何を修了したかをすでに Salesforce に記録しています。コースの受講登録が「Completed」に変わる、パートナーアカウントが認定ティアに到達する、顧客がオンボーディングを終える。レコードは更新されますが、その後、誰かがリストをエクスポートし、1 週間後に手作業で修了証をメール送信している、というのがよくある光景です。

このガイドでは、そのギャップを埋める方法を紹介します。レコードが目的のステータスに達したら、Salesforce が https://badges.ninja の API を呼び出し、Open Badge v2.0 のクレデンシャルが発行され、検証リンクがレコードに書き戻されます。そのリンクは、メールテンプレートやレポート、Experience Cloud のページにそのまま貼り付けられます。

必要なものは、Salesforce の管理者(または開発者向けサンドボックス)、https://badges.ninja ですでにデザイン済みのバッジ、そして API キーです。API を一度も呼び出したことがない場合は、まず API クイックスタートをお読みください。10 分で終わります。

CRM トリガーが適しているのはどんなときか

Apex を書く前に、本当に必要かどうかを確認しましょう。学期ごと、四半期ごとにまとめてクレデンシャルを発行するなら、ダッシュボードの CSV アップロードのほうが速く、コードもまったく不要です。Salesforce を使う方法が真価を発揮するのは、次のような場合です。

  • レコードが変わった瞬間に、人の手を介さず発行したい。
  • 「誰が取得したか」というデータがすでに Salesforce にあり、二つ目の信頼できる情報源を作りたくない。
  • クレデンシャルのリンクを、Contact、Lead、またはカスタムの研修レコードに保存してレポートに使いたい。

ワークフローツールが Apex ではなく Zapier なら、同じ API 呼び出しがそちらでも使えます。詳しくは Zapier による Open Badges 自動化をご覧ください。

作るもの

このフローは 4 つの要素で構成されます。

  1. バッジ:ビジュアルデザイナーで作成します。その id(GET /badges が返す id)をコピーしておきます。
  2. API キー:ダッシュボードの API Keys ページで作成します。キーは一度しか表示されないので、すぐに保管してください。
  3. Named Credential:Salesforce 側でキーを保持し、コードにキーが現れないようにします。
  4. Apex トリガーと Queueable クラス:アワードを発行し、アワードのリンクをレコードに書き戻します。

Newly created API key revealed once

ステップ 1:キーを Named Credential に保存する

API キーを Apex にハードコードするのは、避けるべき失敗です。Setup でカスタムヘッダー付きの External Credential を作成し、続けて API ホストを指す Named Credential を作成します。

  • URL: https://api.badges.ninja
  • カスタムヘッダー名: X-Api-Key
  • カスタムヘッダー値: bws_YOUR_KEY_HERE の形式のキー
  • Named Credential 名: BadgesNinja

自動化を実行する権限セットに、External Credential のプリンシパルへのアクセス権を付与してください。Named Credential があれば、別途 Remote Site Setting は不要です。Salesforce がクレデンシャル経由でコールアウトを認可します。組織がまだ従来型の Named Credential やプレーンなエンドポイントを使っている場合は、https://api.badges.ninja を Remote Site Setting として追加し、ヘッダーはコードから送信します。

ステップ 2:レコードにフィールドを追加する

達成を表すオブジェクト(ここでは Training_Enrollment__c というカスタムオブジェクトを使います)に、次のフィールドを追加します。

  • Badge_Id__c(テキスト):発行するバッジ。コースごとに保存しておけば、1 つのトリガーで多数のバッジに対応できます。
  • Badge_Award_Id__c(テキスト、一意):API が返すアワード id。
  • Badge_Url__c(URL):公開の検証ページ。
  • Badge_Error__c(ロングテキスト):直近のエラー。デバッグログを開かなくても、管理者が失敗を確認できます。

Badge_Award_Id__c フィールドは二つの役目を果たします。重複発行の防止策でもあり、トリガーはこれが空のときだけ発行します。

ステップ 3:Apex コールアウト

トリガーから直接コールアウトを行うことはできません。コミット前の DML と同じトランザクション内ではコールアウトが許可されていないためです。処理は、Database.AllowsCallouts を実装した Queueable に任せます。

POST /awards の API 仕様は短くまとまっています。ボディには badgeId、name と email を持つネストした recipient オブジェクト、そして必須の issuedOn を含めます。成功すると HTTP 200 と { "awardId": "..." } が返ります。受取人の名前は 5 文字以上が必要なので、たとえば「Al Li」という Contact は拒否されます。呼び出す前に検証してください。

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 が含まれないため、コード側でアワード id から https://badges.ninja/awards/<awardId> を組み立てていることです。

ステップ 4:トリガー

トリガーは薄く保ちます。対象となるレコードを判定し、レコードごとに 1 つのジョブをキューに入れるだけです。

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 のチェックは重要です。これがないと、修了済みレコードを後から編集するたびに処理が再実行されてしまいます。ジョブ内のアワード id が空かどうかのガードと組み合わせることで、1 つのレコードが生み出せるクレデンシャルは必ず 1 つだけになります。

守るべき制限が一つあります。Salesforce では、1 つのトランザクションからキューに入れられる Queueable ジョブの数に小さな上限があります(同期コンテキストでは 50 件)。2,000 行のデータインポートのように修了データを一括で読み込むと、上記のトリガーはこの上限に達します。一括読み込みの場合は、代わりにダッシュボードの CSV 一括アワードでリストをアップロードするか、Database.AllowsCallouts を備えた Batchable クラスに切り替え、スコープごとに少数のレコードを処理してください。

API keys list showing the masked prefix

エラー処理とリトライ

API は標準的なステータスコードを返します。コードごとに、取るべき対応は異なります。

ステータス意味対応
400フィールドの欠落または不正(5 文字未満の受取人名を含む)リトライしないでください。データを修正し、メッセージを Badge_Error__c に書き込みます。
401API キーが不正、または未指定Named Credential を確認してください。修正するまでリトライしても無意味です。
402プランのクレデンシャル数またはクォータの上限に到達発行を止めて管理者に通知します。プランをアップグレードするか、クォータがリセットされてから再試行してください。
404badgeId が存在しないレコードまたはコースの Badge_Id__c を修正してください。
5xx またはタイムアウト一時的な障害後でリトライしても問題ありません。

ジョブはエラーをレコードに書き込み、Badge_Award_Id__c を空のままにするので、リトライはチェックボックスを切り替えるか、レコードを再保存するだけで済みます。修了済みで、アワード id がなく、一時的なエラーが記録されている受講登録を探して再度キューに入れる小さなスケジュール Apex クラスを用意すれば、追加のインフラなしで自動リトライのキューが手に入ります。永久に不正なレコードが無限ループしないよう、カウンターフィールドで試行回数に上限を設けてください。

本番に接続する前に、プランを確認しましょう。Free プランは年 1,200 件のクレデンシャルまで、Starter は月額 $9、Pro は月額 $29 です。すべての Lead で発火するトリガーでは、小さなプランはすぐに使い切ってしまいます。条件は、本当の達成だけに絞ってください。

Salesforce 内でクレデンシャルを見えるようにする

Badge_Url__c が埋まれば、どこでも使えます。

  • メールアラートとテンプレート: 「おめでとうございます。こちらがクレデンシャルです」というメールにこのフィールドを差し込みます。
  • レポート: Badge_Award_Id__c != null のレポートで、コース、地域、アカウント所有者ごとの発行数がわかります。
  • Experience Cloud: パートナーの認定ページにリンクを表示すれば、取引先が自分で検証できます。
  • ページレイアウト: URL フィールドを追加すれば、サポート担当者が「彼女はバッジを受け取った?」という質問にひと目で答えられます。

検証ページは公開されており、サインアップは不要です。これこそが要点です。受取人、採用担当者、顧客の調達チームなど、誰でもリンクを開いて、誰が発行したのか、何を証明するのか、いつ発行されたのかを確認できます。機械可読なチェックには、あらゆるアワードの Open Badge v2.0 JSON-LD を https://api.badges.ninja/certify-badge/award/<awardId> で取得できます。

受取人には、固有の検証 URL、QR コード、A4 の PDF 証明書、そして受取人ポータルからの Add to LinkedIn オプションといった、おなじみの特典も用意されています。ポータルへのサインインはパスワードではなくマジックリンクで行います。

AppExchange のバッジプラグインより優れている理由

AppExchange のバッジプラグインは、Salesforce のシート数や、マネージドパッケージのライセンス単位で課金されることが多くなっています。そのコストは、発行するクレデンシャルの数ではなく、Salesforce チームの規模に応じて増えます。そのため、10 人の研修チームと 1 万人の受講者を抱えるプログラムとでは、同じ成果に対して支払う金額が大きく変わります。

API 連携なら、このモデルが逆転します。支払いはクレデンシャルの発行数に応じた定額プランで、トリガーのコードは自分で読んで変更でき、出力は標準の Open Badge v2.0 なので、受取人が独自ビューアに縛られることもありません。デザインの自由度も保てます。ビジュアルデザイナーには 80 以上のテンプレート、シェイプ、アイコン、カスタムアップロードがあり、パッケージ内の固定レイアウトに縛られません。

正直なトレードオフもあります。プラグインはクリックだけでインストールでき、Apex は不要です。数十行のコードを保守できる人がチームにいないなら、プラグインやノーコードのコネクタのほうが合っているかもしれません。連携ではなくプラットフォーム全般を比較しているなら、Credly の代替の解説で、料金と機能の違いを並べて確認できます。

本番前のテスト

まずはサンドボックスで、フロー全体を動かしてみましょう。

  1. テスト用のバッジと、自分が受信できるメールアドレスのテスト用受取人を作成します。
  2. Apex テストでは、ステータス 200 と {"awardId":"test-award-id"} を返す HttpCalloutMock を実装します。Salesforce はテストコンテキストでの実際のコールアウトを許可しておらず、デプロイにはこのカバレッジが必要です。
  3. サンドボックスで、受講登録の 1 件を Completed に設定し、1 分以内に Badge_Award_Id__c と Badge_Url__c が埋まることを確認します。
  4. プライベートブラウザウィンドウで URL を開き、サインインなしで公開の検証ページが表示されることを確認します。
  5. わざと失敗させ(バッジ id を空にする、または 5 文字未満の受取人名を使う)、エラーが Badge_Error__c に入ることを確認します。

API キーがデバッグログやスクリーンショットに漏れた場合は、キーをローテーションしてください。失効は API Keys ページから即座に行え、更新が必要なのは External Credential だけで、コードは変更しなくて済みます。

まとめ

パターンはシンプルです。薄いトリガーが「いつ」を判断し、Queueable がコールアウトを行い、Named Credential がキーを保持し、レコード上の 3 つのフィールドが結果を保存します。研修のマイルストーン、パートナーのティア、イベント参加など、Salesforce で表現できるあらゆる状態変化に使えます。

まずは 1 つのオブジェクトと 1 つのバッジで始め、1 週間ほど様子を見て、リトライの挙動がデータに合ってきたら広げていきましょう。

最初の検証可能なクレデンシャルを発行する準備はできましたか? 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.

ブログに戻る

関連記事