Salesforce Open Badges:由 CRM 觸發器簽發可驗證憑證

透過 Apex 呼叫 Badges Ninja API,讓任何 Salesforce 物件事件——Lead 成交、Account 升級、培訓里程碑——自動觸發 Open Badge 簽發。

Nacho Coll 作者 更新於 14 分鐘閱讀
透過 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 自動化。

你要建置什麼

整個流程由四個部分組成:

  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

在 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 keys list showing the masked prefix

錯誤處理與重試

API 會回傳標準狀態碼,每一種都需要你的程式碼做出不同的回應:

狀態碼意義處理方式
400欄位缺漏或無效,包括接收者姓名少於五個字元不要重試。修正資料,並把訊息寫入 Badge_Error__c。
401API 金鑰錯誤或缺漏檢查 Named Credential。在修正之前重試沒有用。
402你的方案憑證數量或配額已用完停止簽發並通知管理員;升級方案或配額重置後再重試。
404badgeId 不存在修正紀錄或課程上的 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 替代方案一文並排比較了價格和功能差異。

上線前的測試

先在沙箱中完整跑一遍流程。

  1. 建立一枚測試徽章,以及一位使用你可控電子郵件地址的測試接收者。
  2. 在 Apex 測試中,實作 HttpCalloutMock,回傳狀態碼 200 和 {"awardId":"test-award-id"};Salesforce 不允許在測試環境中發出真實的 callout,而你需要這部分覆蓋率才能部署。
  3. 在沙箱中,把一筆報名紀錄設為 Completed,並檢查 Badge_Award_Id__c 和 Badge_Url__c 是否在一分鐘內被填入。
  4. 在無痕瀏覽器視窗中開啟該 URL,確認公開驗證頁面不需登入即可顯示。
  5. 刻意製造一次失敗(清空徽章 id,或使用少於五個字元的接收者姓名),並確認錯誤出現在 Badge_Error__c 中。

如果 API 金鑰曾洩漏到除錯記錄或螢幕截圖中,請立即輪替。在 API Keys 頁面可以即時撤銷,而你只需更新 External Credential,不必更動程式碼。

總結

這個模式很小巧:精簡的觸發器決定何時簽發,Queueable 負責呼叫,Named Credential 保管金鑰,紀錄上的三個欄位儲存結果。它適用於培訓里程碑、合作夥伴等級、活動出席,或任何你能在 Salesforce 中表達的狀態變化。

從一個物件和一枚徽章開始,觀察一週,等重試行為符合你的資料後再擴充。

準備好簽發你的第一份可驗證憑證了嗎? 在 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.

返回部落格

相關文章