무거운 플러그인 없이 WordPress에서 Open Badges 발급하기

완료 시점에 Badges Ninja API를 호출해서 어떤 WordPress 코스·LMS 플러그인(LearnDash, LifterLMS, Tutor)에서도 Open Badges를 발급하세요 — 별도의 배지 플러그인을 설치할 필요가 없어요.

Nacho Coll 작성자 업데이트됨 23 분 읽기
완료 시점에 Badges Ninja API를 호출해서 어떤 WordPress 코스·LMS 플러그인(LearnDash, LifterLMS, Tutor)에서도 Open Badges를 발급하세요 — 별도의 배지 플러그인을 설치할 필요가 없어요.

WordPress에서 코스를 운영하고 있다면 이 흐름이 익숙할 거예요. 누군가 LearnDash, LifterLMS, Tutor LMS에서 모듈을 완료하면 LMS는 “완료”로 표시하고… 그다음엔 검증 가능한 일이 아무것도 일어나지 않아요. PDF를 이메일로 보낼 수도 있고, 자체 데이터베이스 테이블과 자체 이미지 라이브러리, 그리고 “배지”가 무엇인지에 대한 자체적인 정의를 가진 전용 WordPress 배지 플러그인을 설치할 수도 있어요 — 사이트 밖의 누구도 검증할 수 없는 것 말이죠.

더 간단한 방법이 있어요. WordPress 코스 플러그인들은 누군가 코스를 완료하는 순간 이미 액션 훅을 실행해요. 그 훅을 잡아서 Badges Ninja API에 HTTP 호출을 한 번 하면 돼요. 추가 플러그인도, 추가 호스팅도, 독점적인 배지 형식도 필요 없어요 — 수신자가 어디서든 검증할 수 있고, LinkedIn에 추가할 수 있고, PDF로 다운로드할 수 있는 표준 준수 Open Badge v2.0 자격증명만 있으면 돼요.

이 글에서는 먼저 패턴을 한 번 설명한 다음, LearnDash, LifterLMS, Tutor LMS 각각에 대한 구체적인 훅을 보여드릴게요.

왜 WordPress 배지 플러그인을 그냥 쓰면 안 될까요?

전용 WordPress 배지 플러그인은 겉보기보다 더 좁은 문제를 해결해요. 사이트 내 “내 성과” 페이지에 이미지를 렌더링하는 데는 능숙하지만, 자격증명에 실제로 중요한 부분에서는 대체로 훨씬 약해요.

  • 이식성. WordPress 사이트의 숏코드로만 존재하는 배지는 자격증명이 아니라 장식이에요. 수신자가 이직하거나, 이메일 계정을 바꾸거나, 사이트가 다운되는 순간 그 “증거”도 함께 사라져요.
  • 검증. 누구든 WordPress 관리자에 로그인하지 않고도 자격증명이 진짜인지 확인할 수 있어야 해요. Open Badge v2.0은 설계상 공개 검증 URL과 기계가 읽을 수 있는 JSON-LD를 제공해요.
  • 유지보수 영역. WordPress 플러그인이 하나 늘어날 때마다 패치해야 할 대상, 백업해야 할 데이터베이스 테이블, 코어 업데이트 시 장애 지점이 하나씩 더 늘어나요.

코스 완료 시 외부 API를 호출하면 이 세 가지 문제를 모두 피할 수 있어요. WordPress는 콘텐츠 전달과 진행 상황 추적이라는 잘하는 일에 계속 집중하고, 자격증명 자체는 발급·검증·공유를 위해 특별히 만들어진 시스템에 존재하게 돼요.

패턴: 훅 하나, HTTP 호출 하나

주요 WordPress LMS 플러그인은 모두 사용자가 코스를 완료할 때 액션을 실행해요. 통합 과정은 항상 같은 세 단계예요.

  1. 사용 중인 LMS에 맞는 완료 액션에 훅을 건다.
  2. 수신자의 신원을 조회(또는 생성)한다 — 보통 WordPress 계정의 이메일과 표시 이름이면 충분해요.
  3. 배지 ID와 수신자 정보를 전달해 wp_remote_post()로 Badges Ninja API의 POST /awards를 호출한다.

먼저 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' );

Newly created key revealed once

이 키는 서버 사이드에 두세요. 이 가이드의 모든 내용은 WordPress 서버에서 PHP로 실행돼요 — wp_remote_post()는 서버 사이드 호출이고, wp-config.php는 브라우저로 전송되지 않아요. 바로 그게 원하는 상태예요. 키는 HTTPS를 통해 서버 간에만 이동하고, 사이트 방문자 누구도 이를 읽을 수 없어요. 이 호출을 클라이언트 사이드 JavaScript로 옮기는 일은 절대 하지 마세요 — 키를 인라인으로 포함한 브라우저의 fetch() 호출은 누구든 브라우저 개발자 도구를 통해 키를 노출시킬 수 있어요. 지켜야 할 습관이 두 가지 있어요. 호스트가 wp-config.php를 실행된 PHP로 서빙하는지(절대 평문으로가 아니라, 그리고 웹루트에 wp-config.php.bak 사본을 남기지 않는지) 확인하고, 이 통합을 위한 전용 API 키를 별도로 만들어서 혹시 유출되더라도 다른 도구를 망가뜨리지 않고 독립적으로 폐기할 수 있게 하세요.

공유 헬퍼 함수를 사용하면 각 LMS 훅을 얇게 유지할 수 있어요.

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으로 해시 처리되고, 반환되는 응답에는 발급 건의 검증 URL이 포함돼요 — 호출이 성공한 후 WordPress 안에서 바로 “자격증명 보기” 링크를 보여주고 싶을 때 유용해요.

LearnDash

LearnDash는 user와 course를 담은 단일 배열 인자와 함께 learndash_course_completed를 실행해요.

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를 (단일한 전역 BADGES_NINJA_BADGE_ID 상수가 아니라) 코스 메타로 저장해두면, 코스가 두 개 이상이 되는 순간부터 올바른 선택이 돼요 — 각 코스가 자신만의 배지 디자인에 대응하게 되니까요.

LifterLMS

LifterLMS는 $user_id와 $course_id와 함께 lifterlms_course_completed를 실행해요.

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는 코스 ID와 함께 tutor_course_complete_after를 실행하며, 현재 사용자는 세션에서 가져와요.

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()를 해당 훅이 제공하는 사용자 ID로 교체하세요 — 설정 시 한 번은 error_log( print_r( func_get_args(), true ) )로 페이로드를 확인해서 인자를 검증해보세요.

훅을 연결하기 전에 배지 디자인하기

이 모든 것이 실행되기 전에, 먼저 수여할 배지가 있어야 해요. 비주얼 디자이너는 전담 디자이너 없이도 이 작업을 처리할 수 있게 해줘요 — 80개 이상의 도형 템플릿, 커스텀 색상, 아이콘 라이브러리, 로고 업로드까지 모두 그리드에 스냅되죠. 디자이너 사용이 처음이라면, 검증 가능한 첫 디지털 인증서 디자인하기에서 템플릿을 고르는 방법부터 수신자가 실제로 공유하고 싶어할 결과물을 만드는 과정까지 살펴볼 수 있어요.

배지가 만들어지면, 대시보드(또는 GET /badges)에서 ID를 가져와 코스의 커스텀 필드에 넣으세요.

수신자가 실제로 얻는 것

이 부분이 바로 자체 제작한 WordPress 배지 플러그인이 재현할 수 없는 지점이에요. POST /awards가 성공하면 수신자는 다음을 받게 돼요.

  • 누구나 열어볼 수 있는 고유한 검증 URL — WordPress 로그인이 필요 없어요 — 자격증명이 진짜인지, 누가 발급했는지, 언제 발급했는지를 보여줘요.
  • 자동으로 생성되는 QR 코드와 A4 PDF 인증서.
  • 발급자가 LinkedIn 조직 ID를 설정한 경우 표시되는 “LinkedIn 프로필에 추가” 버튼 — 최초 1회 설정은 LinkedIn “프로필에 추가” 버튼 추가하기를 참고하세요.
  • 매직 링크 로그인을 통한 자신만의 수신자 포털(badges.ninja/me) 접근 — 잊어버릴 비밀번호도 없고, WordPress 사이트에서 별도로 관리할 계정도 없어요.
  • 표준을 준수하는 Open Badge v2.0 JSON-LD — Badges Ninja뿐 아니라 OB를 지원하는 어떤 검증기로도 검증할 수 있어요.

이 중 어느 것도 WordPress가 만들어야 할 일이 아니고, WordPress 데이터베이스 안에만 존재해서도 안 돼요.

실패를 우아하게 처리하기

코스 완료 훅은 실제 페이지 요청 중에 실행되므로, 느리거나 실패한 API 호출이 학습자의 “축하합니다” 화면을 막아서는 안 돼요. 개념 증명 단계를 넘어서면 적용할 만한 실용적인 조정이 두 가지 있어요.

실패한 수여가 UI를 막지 않게 하세요. 위의 헬퍼 함수는 이미 예외를 던지는 대신 로그를 남기고 false를 반환해요 — badges_ninja_issue_award()가 무엇을 반환하든 LearnDash, LifterLMS, Tutor는 모두 정상적인 완료 흐름을 계속 진행해요.

대규모라면 인라인 호출 대신 큐에 넣으세요. 단일 코스에 한꺼번에 많은 인원(코호트 시작일, 웨비나 다시 보기 오픈 등)을 통과시킨다면, API를 동기적으로 호출하는 대신 훅에서 WP-Cron 예약 이벤트를 실행하세요.

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 훅 경로를 아예 건너뛰고 CSV를 통한 대량 수여를 사용하세요 — 한 번 업로드하면 플랫폼이 서버 사이드에서 페이지네이션을 처리하며 일시정지/재개 기능도 내장되어 있어요.

멱등성에 관한 참고 사항

만약 여러분의 LMS가 동일한 사용자에 대해 완료 훅을 두 번 이상 실행하는 경우가 있다면(퀴즈 재응시와 관련된 흔한 LearnDash 엣지 케이스나 Tutor LMS 재동기화 등), 같은 배지를 두 번 수여하고 싶지는 않을 거예요. 가장 간단한 방어책은 호출이 성공한 직후 설정하는 WordPress 사용자 메타 플래그예요.

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 );
}

정말 대량으로 발급하고 있어서 사이트별이 아니라 중앙에서 이를 처리하고 싶다면, 웹훅 기반 이벤트 처리나 모든 엔드포인트의 요청/응답 형식을 다루는 API 퀵스타트를 확인해보세요.

이 패턴이 단일 코스를 넘어설 때

위의 예시들은 모두 코스당 배지 하나를 전제로 해요. 여러 코스 배지를 딴 후 잠금 해제되는 트랙 배지나, 여러 선행 조건에 의존하는 캡스톤 배지처럼 누적형 자격증명을 운영하게 되면, “어떤 배지를, 어떤 완료에 대해”라는 매핑을 포스트 메타에서 작은 조회 테이블로 옮기거나, 동일한 POST /awards 호출을 실행하는 스프레드시트 기반 자동화(Airtable나 Google 스프레드시트)로 구동하세요. API 호출 자체는 변하지 않아요 — 언제 그것을 호출할지 결정하는 부분만 달라질 뿐이에요.

통합 문제 해결하기

이 작업을 처음 연결할 때 사람들이 겪는 마찰의 대부분은 몇 가지 문제로 좁혀져요.

훅이 전혀 실행되지 않아요. 사용 중인 플러그인의 실제 버전에 맞는 올바른 액션에 훅을 걸었는지 확인하세요 — 특히 LearnDash는 메이저 릴리스마다 완료 훅 이름을 변경해온 이력이 있어요(오래된 설치본은 여전히 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 타임스탬프를 기대해요 — 위의 헬퍼 함수는 정확히 그것을 전송해요. 가장 흔한 실수는 수신자의 name이에요. 최소 5자 이상이어야 하므로 “Jo” 같은 WordPress 표시 이름은 거부돼요. 표시 이름이 너무 짧다면 계정의 전체 이름(또는 이메일의 로컬 파트)으로 대체하세요.

401 Unauthorized. API 키 상수가 로드되지 않았거나, 상수를 설정한 후 키가 재발급됐어요. 키는 생성 시 한 번만 표시되고 다시 조회할 수 없어요 — 어떤 키가 유효한지 알 수 없게 됐다면, 추측하지 말고 오래된 키를 삭제하고 새 키를 발급하세요.

배지는 발급됐지만 원하지 않은 배지 디자인이에요. 이건 거의 항상 테스트 중에 남은 오래된 badges_ninja_badge_id 포스트 메타 값 때문이에요. 매핑이 코스 게시물에 저장되어 있어서, 새로운 코호트를 위해 코스를 복제한 후 값을 바꿨다는 사실을 잊기 쉬워요 — 코스를 복제할 때마다 한 번씩 확인해볼 가치가 있어요.

같은 사람에게 중복으로 수여돼요. 위에서 사용자 메타 방어책으로 다뤘지만 다시 한번 강조할게요. LMS 완료 훅이 정확히 한 번만 실행된다는 보장은 없어요. “이미 수여됨”을 무시해야 할 예외적인 경우가 아니라 확인해야 할 정상적인 케이스로 취급하세요.

자주 묻는 질문

WooCommerce 코스 번들에서도 작동하나요? 네 — 번들 구매가 코스 완료와 무관하게 배지를 발급해야 하는 경우(예: 특정 번들을 구매한 모든 사람에게 “인증 회원” 배지 부여), LMS 완료 액션 대신 woocommerce_order_status_completed에 훅을 걸고, 코스가 아니라 상품 메타에서 배지 ID를 조회하세요.

수신자가 WordPress 계정 없이도 배지를 받을 수 있나요? 네, 그게 핵심이에요. 수여는 WordPress 사용자 레코드가 아니라 Badges Ninja 내의 이메일 주소에 연결돼요. 사이트에서 게스트 결제나 익명 퀴즈 완료를 허용한다면, badges_ninja_issue_award()에 전달할 실제 이메일 주소를 확보하고 있는지만 확인하세요.

수여를 내보내기 전에 검토하고 싶다면요? 완료 훅에서 직접 호출을 실행하지 마세요. 대신 대기 중인 수여(사용자, 코스, 타임스탬프)를 커스텀 테이블이나 포스트 타입에 기록하고, 예약된 작업이나 필요할 때 동일한 헬퍼 함수를 호출하는 관리자용 “승인 및 수여” 버튼을 통해 발급하세요.

코스마다 별도의 배지가 필요한가요, 아니면 배지 하나로 여러 코스를 커버할 수 있나요? 둘 다 가능해요. 일부 발급자는 세분화를 위해 코스마다 배지 하나를 만들고, 다른 발급자는 단일 “프로그램 완료” 배지를 만들어 필요한 모든 코스가 끝났을 때만 한 번 수여해요. 이 경우 API를 호출하기 전에 훅 안에서 선행 조건 완료 여부를 확인해요.


첫 번째 검증 가능한 자격증명을 발급할 준비가 되셨나요? 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.

블로그로 돌아가기

관련 글