Salesforce Open Badges:由 CRM 觸發器簽發可驗證憑證
透過 Apex 呼叫 Badges Ninja API,讓任何 Salesforce 物件事件——Lead 成交、Account 升級、培訓里程碑——自動觸發 Open Badge 簽發。
多數培訓團隊早已在 Salesforce 裡記錄了誰完成了什麼。課程報名狀態變成 “Completed”,合作夥伴帳號達到某個認證等級,客戶完成了導入流程。紀錄變了,然後一週後有人匯出名單,手動用電子郵件寄出證書。
本指南教你補上這個缺口:當紀錄達到你在意的狀態時,Salesforce 呼叫 https://badges.ninja 的 API,簽發一份 Open Badge v2.0 憑證,並把驗證連結寫回該筆紀錄。之後你可以把這個連結放進郵件範本、報表或 Experience Cloud 頁面。
你需要一位 Salesforce 管理員(或一個開發者沙箱)、一枚已在 https://badges.ninja 上設計好的數位徽章,以及一組 API 金鑰。如果你從未呼叫過這個 API,請先閱讀 API 快速入門,只需十分鐘。
什麼時候適合用 CRM 觸發器
在寫 Apex 之前,先確認你是否真的需要它。如果你每學期或每季才批次簽發一次憑證,儀表板的 CSV 上傳更快,而且完全不需要程式碼。Salesforce 方案在以下情況才物有所值:
- 紀錄一變更就應立即簽發,不需要任何人工步驟。
- 「誰獲得了憑證」的資料本來就在 Salesforce 裡,你不想再維護第二個資料來源。
- 你希望把憑證連結存放在 Contact、Lead 或自訂的培訓紀錄上,方便製作報表。
如果你的工作流程工具是 Zapier 而不是 Apex,同樣的 API 呼叫在那裡也能用;請參閱 Zapier Open Badges 自動化。
你要建置什麼
整個流程由四個部分組成:
- 一枚徽章,在視覺化設計器中設計。複製它的 id(即
GET /badges回傳的id)。 - 一組 API 金鑰,在儀表板的 API Keys 頁面建立。金鑰只會顯示一次,所以請立刻存好。
- 一個 Named Credential,位於 Salesforce 中,用來存放金鑰,讓它永遠不會出現在程式碼裡。
- 一個 Apex 觸發器加一個 Queueable 類別,負責簽發憑證,並把簽發連結寫回紀錄。

步驟 1:把金鑰存進 Named Credential
在 Apex 中寫死 API 金鑰是必須避免的錯誤。在 Setup 中,先建立一個帶自訂標頭的 External Credential,再建立一個指向 API 主機的 Named Credential:
- URL:
https://api.badges.ninja - 自訂標頭名稱:
X-Api-Key - 自訂標頭值: 你的金鑰,格式為
bws_YOUR_KEY_HERE - Named Credential 名稱:
BadgesNinja
請讓執行自動化的權限集(permission set)擁有存取該 External Credential 主體(principal)的權限。有了 Named Credential,你就不需要另外的 Remote Site Setting;Salesforce 會透過該憑證授權呼叫。如果你的組織仍在使用舊版 Named Credential 或單純的端點,請把 https://api.badges.ninja 加為 Remote Site Setting,並在程式碼中送出標頭。
步驟 2:為紀錄新增欄位
在代表成就的物件上(我們使用一個名為 Training_Enrollment__c 的自訂物件),新增:
Badge_Id__c(Text):要簽發的徽章。依課程存放它,可以讓一個觸發器服務多枚徽章。Badge_Award_Id__c(Text,unique):API 回傳的簽發 id。Badge_Url__c(URL):公開的驗證頁面。Badge_Error__c(Long Text):最近一次錯誤,讓管理員不必開啟除錯記錄就能看到失敗原因。
Badge_Award_Id__c 欄位一物兩用。它是你的防重複保護:只有當它為空時,觸發器才會簽發。
步驟 3:Apex 呼叫
觸發器不能直接發出呼叫,因為在存在未提交 DML 的同一個交易中不允許 callout。請把工作交給實作了 Database.AllowsCallouts 的 Queueable。
POST /awards 的 API 規格很簡短。請求本文包含 badgeId、一個巢狀的 recipient 物件(帶 name 和 email),以及必填的 issuedOn。呼叫成功時會回傳 HTTP 200 和 { "awardId": "..." }。接收者姓名至少要有五個字元,因此名為 “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:觸發器
讓觸發器保持精簡。它只負責判斷哪些紀錄符合條件,並為每筆紀錄排入一個工作。
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 為空」的保護,一筆紀錄最多只會產生一份憑證。
還有一個需要遵守的限制:Salesforce 只允許在單一交易中排入少量 Queueable 工作(同步環境中為 50 個)。如果你批次載入完成紀錄,例如一次匯入 2,000 列資料,上面的觸發器就會碰到這個上限。對於大量載入,要嘛改用儀表板的 CSV 批次簽發來上傳名單,要嘛改用帶 Database.AllowsCallouts 的 Batchable 類別,每個 scope 只處理少量紀錄。

錯誤處理與重試
API 會回傳標準狀態碼,每一種都需要你的程式碼做出不同的回應:
| 狀態碼 | 意義 | 處理方式 |
|---|---|---|
| 400 | 欄位缺漏或無效,包括接收者姓名少於五個字元 | 不要重試。修正資料,並把訊息寫入 Badge_Error__c。 |
| 401 | API 金鑰錯誤或缺漏 | 檢查 Named Credential。在修正之前重試沒有用。 |
| 402 | 你的方案憑證數量或配額已用完 | 停止簽發並通知管理員;升級方案或配額重置後再重試。 |
| 404 | badgeId 不存在 | 修正紀錄或課程上的 Badge_Id__c。 |
| 5xx 或逾時 | 暫時性故障 | 可以稍後安全重試。 |
由於工作會把錯誤寫到紀錄上,並讓 Badge_Award_Id__c 保持為空,重試就像勾選一個核取方塊或重新儲存紀錄一樣簡單。再寫一個小型的排程 Apex 類別,找出已完成但沒有簽發 id 且帶有暫時性錯誤的報名紀錄並重新排入,你就擁有一個不需額外基礎設施的自動重試佇列。請用計數欄位限制嘗試次數,避免永久出錯的紀錄無限循環。
接上正式環境之前,請先確認你的方案。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 code、A4 PDF 證書,以及接收者入口網站中的「加入 LinkedIn」選項,該入口網站使用魔法連結而非密碼登入。
為什麼這比 AppExchange 徽章外掛更划算
AppExchange 上的徽章外掛通常按 Salesforce 席次或受管套件授權計費。這筆成本隨你的 Salesforce 團隊規模成長,而不是隨你簽發的憑證數量成長,因此十人的培訓團隊和一萬名學員的專案,為同樣的產出付出的費用差異很大。
API 整合則翻轉了這種模式。你依憑證數量以固定方案付費,觸發器程式碼可以自由閱讀和修改,輸出是標準的 Open Badge v2.0,接收者不會被綁定在專屬的檢視器裡。你也保有設計自由:視覺化設計器提供 80 多種範本、形狀、圖示和自訂上傳,而不是套件內固定的版面。
坦白說,這裡有個取捨:外掛點擊即可安裝,不需要 Apex。如果團隊裡沒有人能維護幾十行程式碼,外掛或無程式碼連接器可能更合適。如果你比較的是整體平台而不是整合方式,Credly 替代方案一文並排比較了價格和功能差異。
上線前的測試
先在沙箱中完整跑一遍流程。
- 建立一枚測試徽章,以及一位使用你可控電子郵件地址的測試接收者。
- 在 Apex 測試中,實作
HttpCalloutMock,回傳狀態碼 200 和{"awardId":"test-award-id"};Salesforce 不允許在測試環境中發出真實的 callout,而你需要這部分覆蓋率才能部署。 - 在沙箱中,把一筆報名紀錄設為 Completed,並檢查
Badge_Award_Id__c和Badge_Url__c是否在一分鐘內被填入。 - 在無痕瀏覽器視窗中開啟該 URL,確認公開驗證頁面不需登入即可顯示。
- 刻意製造一次失敗(清空徽章 id,或使用少於五個字元的接收者姓名),並確認錯誤出現在
Badge_Error__c中。
如果 API 金鑰曾洩漏到除錯記錄或螢幕截圖中,請立即輪替。在 API Keys 頁面可以即時撤銷,而你只需更新 External Credential,不必更動程式碼。
總結
這個模式很小巧:精簡的觸發器決定何時簽發,Queueable 負責呼叫,Named Credential 保管金鑰,紀錄上的三個欄位儲存結果。它適用於培訓里程碑、合作夥伴等級、活動出席,或任何你能在 Salesforce 中表達的狀態變化。
從一個物件和一枚徽章開始,觀察一週,等重試行為符合你的資料後再擴充。
準備好簽發你的第一份可驗證憑證了嗎? 在 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.

