Salesforce と Open Badges:CRM のトリガーで検証可能なクレデンシャルを発行する
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 つの要素で構成されます。
- バッジ:ビジュアルデザイナーで作成します。その id(
GET /badgesが返すid)をコピーしておきます。 - API キー:ダッシュボードの API Keys ページで作成します。キーは一度しか表示されないので、すぐに保管してください。
- Named Credential:Salesforce 側でキーを保持し、コードにキーが現れないようにします。
- Apex トリガーと Queueable クラス:アワードを発行し、アワードのリンクをレコードに書き戻します。

ステップ 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 は標準的なステータスコードを返します。コードごとに、取るべき対応は異なります。
| ステータス | 意味 | 対応 |
|---|---|---|
| 400 | フィールドの欠落または不正(5 文字未満の受取人名を含む) | リトライしないでください。データを修正し、メッセージを Badge_Error__c に書き込みます。 |
| 401 | API キーが不正、または未指定 | Named Credential を確認してください。修正するまでリトライしても無意味です。 |
| 402 | プランのクレデンシャル数またはクォータの上限に到達 | 発行を止めて管理者に通知します。プランをアップグレードするか、クォータがリセットされてから再試行してください。 |
| 404 | badgeId が存在しない | レコードまたはコースの 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 の代替の解説で、料金と機能の違いを並べて確認できます。
本番前のテスト
まずはサンドボックスで、フロー全体を動かしてみましょう。
- テスト用のバッジと、自分が受信できるメールアドレスのテスト用受取人を作成します。
- Apex テストでは、ステータス 200 と
{"awardId":"test-award-id"}を返すHttpCalloutMockを実装します。Salesforce はテストコンテキストでの実際のコールアウトを許可しておらず、デプロイにはこのカバレッジが必要です。 - サンドボックスで、受講登録の 1 件を Completed に設定し、1 分以内に
Badge_Award_Id__cとBadge_Url__cが埋まることを確認します。 - プライベートブラウザウィンドウで URL を開き、サインインなしで公開の検証ページが表示されることを確認します。
- わざと失敗させ(バッジ 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 出力。クレジットカードは不要です。
この記事の作成方法
このブログの記事の一部は、AIアシスタントの支援を受けて下書きされた後、公開前にBadges Ninjaチームがレビュー、ファクトチェック、編集を行っています。すべてのコードサンプルと価格は実際の製品と照合して検証済みです。編集方針とAIプロセスについて詳しくは、 編集プロセスページ .

著者について
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.

