在 WordPress 中簽發 Open Badges(不需要笨重的外掛)
只要在任何 WordPress 課程/LMS 外掛(LearnDash、LifterLMS、Tutor)完成時呼叫 Badges Ninja API,就能簽發 Open Badges——不必另外安裝徽章外掛。
如果你在 WordPress 上經營課程,你一定很熟悉這套流程:有人在 LearnDash、LifterLMS 或 Tutor LMS 完成了一個單元,LMS 把它標記為「已完成」,然後……什麼可驗證的事都沒發生。或許你會寄一封附 PDF 的信。或許你會安裝一個專門的 WordPress 徽章外掛,它會新增自己的資料庫表格、自己的圖片庫,還有它自己對「徽章」是什麼的認知——一種你網站之外沒人能驗證的認知。
其實有一條更簡單的路。WordPress 課程外掛在使用者完成課程的那一刻,就已經會觸發一個 action hook。你可以接住這個 hook,然後對 Badges Ninja API 發出一次 HTTP 呼叫。不需要額外的外掛,不需要額外的主機空間,不需要私有的徽章格式——只有一份符合標準的 Open Badge v2.0 憑證,領受者可以在任何地方驗證它、加到 LinkedIn 上,並下載成 PDF。
這篇文章會先講一次這套通用模式,再分別展示 LearnDash、LifterLMS 和 Tutor LMS 各自具體的 hook。
為什麼不直接用 WordPress 徽章外掛?
專門的 WordPress 徽章外掛所解決的問題,比它們看起來要解決的問題窄得多。它們擅長在你網站內的「我的成就」頁面上顯示一張圖片。但在真正攸關一份憑證價值的事情上,它們通常弱得多:
- 可攜性。 一個只以短代碼形式存在於你 WordPress 網站上的徽章不是憑證——它只是裝飾。一旦領受者換了工作、換了信箱,或你的網站下線了,這份「證明」也就跟著消失。
- 可驗證性。 任何人都應該能在不登入你 WordPress 後台的情況下,確認一份憑證是真的。Open Badge v2.0 從設計上就提供了一個公開的驗證網址,以及機器可讀的 JSON-LD。
- 維護面。 每一個 WordPress 外掛都是多一樣要修補的東西、多一張要備份的資料庫表格、多一個核心更新時可能出狀況的環節。
在課程完成時呼叫外部 API,就繞開了這三個問題。WordPress 繼續負責它擅長的事——內容遞送與進度追蹤——而憑證本身則活在一個專門為簽發、驗證與分享它而打造的系統裡。
這套模式:一個 hook,一次 HTTP 呼叫
每一個主流的 WordPress LMS 外掛,都會在使用者完成課程時觸發一個動作。整合方式始終是同樣的三個步驟:
- 掛上你所用 LMS 對應的完成動作。
- 查詢(或建立)領受者的身分——通常就是他們 WordPress 帳號的電子郵件與顯示名稱。
- 用
wp_remote_post()呼叫 Badges Ninja API 的POST /awards,傳入徽章 ID 與領受者資料。
你得先準備一組 API 金鑰。到你的控制台的 API Keys 下產生一組——完整流程請參見API 金鑰指南。金鑰只會在建立當下顯示一次,所以請把它以常數的形式存放在 wp-config.php 裡,而不是存進資料庫:
define( 'BADGES_NINJA_API_KEY', 'bws_your_32_hex_key_here' );
define( 'BADGES_NINJA_BADGE_ID', 'your-badge-id' );

把這組金鑰留在伺服器端。 本指南裡的一切都跑在你 WordPress 伺服器上的 PHP 中——wp_remote_post() 是一次伺服器端呼叫,而 wp-config.php 從來不會被傳送到瀏覽器。這正是你要的效果:金鑰透過 HTTPS 在伺服器與伺服器之間傳輸,任何訪客都讀不到它。絕對不要把這個呼叫搬到用戶端 JavaScript 裡——從瀏覽器發出、把金鑰內嵌進去的 fetch() 呼叫,會讓任何人透過瀏覽器的開發者工具看到它。有兩個值得養成的習慣:確保你的主機把 wp-config.php 當作已執行的 PHP 來提供服務(絕不能是純文字,也別在網站根目錄留下 wp-config.php.bak 的副本),並為這次整合建立一組專用的 API 金鑰,這樣一旦它外洩,你可以單獨撤銷它——而不影響你的其他工具。
一個共用的輔助函式,能讓每個 LMS 的 hook 都維持精簡:
function badges_ninja_issue_award( $recipient_email, $recipient_name, $badge_id ) {
$response = wp_remote_post( 'https://api.badges.ninja/awards', array(
'headers' => array(
'X-Api-Key' => BADGES_NINJA_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'badgeId' => $badge_id,
'recipient' => array(
'name' => $recipient_name, // must be at least 5 characters
'email' => $recipient_email,
),
'issuedOn' => round( microtime( true ) * 1000 ), // epoch milliseconds
) ),
'timeout' => 15,
) );
if ( is_wp_error( $response ) ) {
error_log( 'Badges Ninja award failed: ' . $response->get_error_message() );
return false;
}
$code = wp_remote_retrieve_response_code( $response );
if ( $code >= 400 ) {
error_log( 'Badges Ninja award failed with HTTP ' . $code . ': ' . wp_remote_retrieve_body( $response ) );
return false;
}
return json_decode( wp_remote_retrieve_body( $response ), true );
}
領受者的電子郵件一送達 Badges Ninja,就會在伺服器端進行一次 SHA-256 雜湊處理,而回傳的內容裡會包含這次頒發的驗證網址——如果你想在呼叫成功之後,直接在 WordPress 裡顯示一個「檢視你的憑證」連結,這會很方便。
LearnDash
LearnDash 會觸發 learndash_course_completed,附帶一個包含 user 與 course 的陣列參數:
add_action( 'learndash_course_completed', function( $data ) {
$user = $data['user'];
$course = $data['course'];
// Map LearnDash course ID → your Badges Ninja badge ID.
// A simple approach: store the badge ID in a course custom field.
$badge_id = get_post_meta( $course->ID, 'badges_ninja_badge_id', true );
if ( ! $badge_id ) {
return; // no badge configured for this course
}
badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
} );
一旦你有超過一門課程,把徽章 ID 存成課程 meta(而不是單一的全域常數 BADGES_NINJA_BADGE_ID)就是正確做法——每門課程都對應自己的徽章設計。
LifterLMS
LifterLMS 會觸發 lifterlms_course_completed,附帶 $user_id 與 $course_id:
add_action( 'lifterlms_course_completed', function( $user_id, $course_id ) {
$user = get_userdata( $user_id );
$badge_id = get_post_meta( $course_id, 'badges_ninja_badge_id', true );
if ( ! $user || ! $badge_id ) {
return;
}
badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
}, 10, 2 );
Tutor LMS
Tutor LMS 會觸發 tutor_course_complete_after,附帶課程 ID,你需要從工作階段中取得目前使用者:
add_action( 'tutor_course_complete_after', function( $course_id ) {
$user_id = get_current_user_id();
$user = get_userdata( $user_id );
$badge_id = get_post_meta( $course_id, 'badges_ninja_badge_id', true );
if ( ! $user || ! $badge_id ) {
return;
}
badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
} );
如果你的 Tutor LMS 設定是從背景程序(例如透過測驗自動評分)觸發完成的,請把 get_current_user_id() 換成你實際使用的 hook 所提供的使用者 ID——設定過程中先用一次 error_log( print_r( func_get_args(), true ) ) 檢查一下傳入的內容,確認參數是什麼。
在接上 hook 之前先設計好徽章
在任何這些動作觸發之前,你需要一個可以頒發的徽章。視覺化設計工具 讓你不必找專職設計師就能搞定這件事——超過 80 種形狀範本、自訂顏色、圖示庫與標誌上傳,全部自動對齊格線。如果你是第一次使用這個設計工具,Designing Your First Verifiable Digital Certificate 會帶你完成挑選範本、做出領受者真的願意分享的成品的整個過程。
徽章建立好之後,從控制台(或透過 GET /badges)取得它的 ID,填進你課程的自訂欄位。
領受者實際上會拿到什麼
這正是一個自製的 WordPress 徽章外掛無法複製的部分。當 POST /awards 成功後,領受者會得到:
- 一個任何人都能開啟的獨一無二驗證網址——不需要 WordPress 登入——顯示這份憑證是真的、由誰簽發、以及何時簽發。
- 一組自動產生的 QR code 與 A4 PDF 證書。
- 一個加到 LinkedIn 個人檔案的按鈕(如果你的簽發者設定了 LinkedIn 組織 ID)——一次性設定請參見 Add a LinkedIn “Add to Profile” Button。
- 透過魔法連結登入,存取他們自己在
badges.ninja/me的領受者入口網站——不用記密碼,也不用在你的 WordPress 網站上再管理一個獨立帳號。 - 符合標準的 Open Badge v2.0 JSON-LD,因此任何支援 OB 的驗證方(不只是 Badges Ninja)都能對其進行驗證。
這些都不是 WordPress 該負責建置的事,也都不該只存在於你的 WordPress 資料庫裡。
妥善處理失敗情況
課程完成的 hook 是在一次即時頁面請求中觸發的,一次緩慢或失敗的 API 呼叫不該卡住學習者的「恭喜」畫面。一旦你從概念驗證階段往前推進,有兩個實用的調整:
別讓失敗的頒發卡住介面。 上面的輔助函式已經是記錄下來並回傳 false,而不是丟出例外——無論 badges_ninja_issue_award() 回傳什麼,LearnDash、LifterLMS 和 Tutor 都會繼續它們原本正常的完成流程。
大規模時用佇列,而不是同步呼叫。 如果你要一次讓一大群學員通過同一門課程(例如某個梯次的開課日,或是網路研討會重播的解鎖),就從 hook 觸發一個 WP-Cron 排程事件,而不是同步呼叫 API:
add_action( 'learndash_course_completed', function( $data ) {
wp_schedule_single_event( time() + 30, 'badges_ninja_deferred_award', array(
$data['user']->user_email,
$data['user']->display_name,
get_post_meta( $data['course']->ID, 'badges_ninja_badge_id', true ),
) );
} );
add_action( 'badges_ninja_deferred_award', 'badges_ninja_issue_award', 10, 3 );
這樣可以把負載分散開來,避免單一次緩慢的請求變成學習者的問題。對於真正大量的批次(整個過去的梯次一次性搬遷),就完全跳過 WordPress hook 這條路,改用透過 CSV 批次頒發——只要上傳一次,平台就會在伺服器端把頒發過程分頁處理,並內建暫停/繼續功能。
關於冪等性的一點說明
如果你的 LMS 有時會針對同一個使用者不只一次觸發完成 hook(在測驗重考時,LearnDash 常見這類邊界情況,或是 Tutor LMS 的重新同步),你當然不會想讓同一枚徽章被頒發兩次。最簡單的防護方式,是在一次成功呼叫之後立刻設定一個 WordPress 使用者 meta 旗標:
if ( get_user_meta( $user_id, 'badges_ninja_awarded_' . $course_id, true ) ) {
return; // already issued
}
$result = badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
if ( $result ) {
update_user_meta( $user_id, 'badges_ninja_awarded_' . $course_id, true );
}
如果你要在真正大的量級上簽發,並希望集中處理而不是各站台各自處理,可以參考由 webhook 驅動的事件處理,或是完整的 API 快速入門,裡面涵蓋了所有端點的請求/回應格式。
當這套模式超出單一課程範圍時
上面的範例都假設一門課程對應一枚徽章。一旦你要處理可疊加的憑證——例如一枚在多枚課程徽章之後才解鎖的學習路徑徽章,或是依賴多個先修條件的畢業專題徽章——就該把「哪枚徽章對應哪次完成」的對應關係,從 post meta 移到一張小型查詢表裡,或是由一個以試算表(Airtable 或 Google Sheets)為基礎的自動化流程來驅動,由它觸發同樣的 POST /awards 呼叫。API 呼叫本身不會變;會變的只是由什麼來決定何時發起它。
排解整合問題
以下這幾個問題,幾乎涵蓋了大家第一次接上這套整合時遇到的大部分困擾:
Hook 一直沒有觸發。 確認你掛上的動作,與外掛實際的版本相符——LearnDash 尤其在多個主要版本之間改過完成 hook 的名稱(較舊的安裝有時仍依賴 learndash_course_completed 從與新版本不同的程式碼路徑觸發)。在你的回呼函式最上方暫時加一行 error_log(),然後以一個可捨棄的測試帳號完成一門測試課程,同時觀察 wp-content/debug.log。
wp_remote_post() 逾時。 共用主機環境有時會封鎖對陌生主機的對外 HTTPS,或是把請求逾時限制在上面設定的 15 秒以下。如果呼叫持續逾時,請向你的主機確認是否允許對 api.badges.ninja 的對外連線,並確認你的 PHP curl 擴充套件擁有最新的 CA 憑證——過期的憑證包,是較舊的共用主機技術堆疊上一種常見的無聲故障。
400 Bad Request。 傳送的內容缺少必填欄位,或是結構不對。POST /awards 預期收到 badgeId、一個包含 name 與 email 的巢狀 recipient 物件,以及一個 issuedOn 時間戳記——上面的輔助函式送出的正是這些。最常見的地雷在領受者的姓名上:它至少要有 5 個字元,所以像「Jo」這樣的 WordPress 顯示名稱會被拒絕。當顯示名稱太短時,改用帳號的全名(或電子郵件的本地端部分)。
401 Unauthorized。 API 金鑰常數沒有被載入,或是在常數設定之後金鑰又被重新產生過。金鑰只會在建立當下顯示一次,之後無法再次取得——如果你已經搞不清楚哪一把金鑰仍在使用,就刪掉舊的、簽發一把新的,而不要用猜的。
徽章已頒發,但設計不對。 這幾乎都是因為測試時留下的 badges_ninja_badge_id post-meta 值已經過時。由於這個對應關係存在課程 post 上,複製課程給新梯次使用之後,很容易忘記自己曾經改過它——每次複製課程時,值得快速檢查一下。
同一個人收到重複的頒發。 上面已經用 user-meta 防護處理過,但值得再說一次:LMS 的完成 hook 並不保證只會觸發一次。把「已經頒發過」當成需要檢查的正常情況,而不是可以忽略的邊界情況。
常見問題
這能跟 WooCommerce 課程套裝組合一起用嗎? 可以——如果一次套裝組合的購買應該獨立於課程完成、直接觸發一枚徽章(例如,任何購買特定套裝組合的人都能拿到一枚「認證會員」徽章),就掛上 woocommerce_order_status_completed,而不是某個 LMS 的完成動作,並從商品 meta 而不是課程中取得徽章 ID。
領受者可以在沒有 WordPress 帳號的情況下拿到徽章嗎? 可以,而這正是重點所在。這次頒發是綁在他們在 Badges Ninja 裡的電子郵件地址上,而不是綁在某筆 WordPress 使用者紀錄上。如果你的網站允許訪客結帳或匿名完成測驗,只要確保你擷取到的是真實的電子郵件地址,並把它傳入 badges_ninja_issue_award()。
如果我想在頒發之前先審核一遍呢? 不要直接從完成 hook 觸發呼叫。改成把待處理的頒發資訊(使用者、課程、時間戳記)寫進一張自訂資料表或自訂文章類型,再從一個排程工作,或是一個按需呼叫同一個輔助函式的後台「核准並頒發」按鈕來完成頒發。
每門課程都需要各自獨立的徽章,還是一枚徽章可以涵蓋好幾門課程? 兩種方式都可行。有些簽發者會為每門課程各自建立一枚徽章,以取得更細的粒度;另一些則只建立一枚「課程全部完成」徽章,並且只在所有必修課程都完成後才頒發,在 hook 內先檢查先修條件是否滿足,再呼叫 API。
準備好簽發你的第一份可驗證憑證了嗎?到 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.
