개발 단계부터 연동
테스트 키로 권한 검증과 웹훅 수신을 먼저 연결하세요. 실제 유료 판매에는 사업자·정산 정보 확인, 새몰의 PG 계약과 운영 개방, 실제 결제·환불 검증이 추가로 필요합니다.
테스트 키는 결제 없이 발급되고, 주문·정산·매출 집계·Analytics에 포함되지 않습니다. 다만 테스트 웹훅도 판매자가 등록한 수신 서버에 도착하므로 테스트 데이터로 분리해 처리하세요.
왜 개발 단계부터인가
제품에 자체 라이선스 서버를 먼저 만들어 출시하면, 나중에 판매 채널을 붙일 때 키 체계와 검증 로직을 다시 맞춰야 할 수 있습니다. 처음부터 제품의 활성화 지점에 verifyLicense(또는 verifyEntitlement)와 결과에 따른 기능 제어를 연결하면:
- 새몰이 제공하는 키 발급·검증·상태 관리 기능을 활용할 수 있습니다
- 테스트·운영 시크릿을 분리해 같은 연동 코드를 사용할 수 있습니다
- 환불·만료·권한 회수 결과를 내 서비스에 반영하도록 미리 준비할 수 있습니다
전체 흐름
| 단계 | 하는 일 | 어디서 |
|---|---|---|
| 1. 상품 등록 | 자동 전달 방식(라이선스·구독 등)을 포함해 상품을 등록합니다. 심사를 통과하기 전에도 연동 시크릿과 테스트 시크릿은 바로 발급됩니다. | 상품 등록 |
| 2. 테스트 키 발급 | 연동 정보 카드에서 테스트 키 발급 (활성 키 상품당 최대 5개) | 상품 수정 → 연동 정보 |
| 3. 개발·검증 | 테스트 시크릿을 개발 환경변수로 두고 verify·웹훅·사용량 리포팅까지 전 구간 개발 | 내 코드 |
| 4. 심사·공개 | 심사를 통과하면 상품이 공개됩니다. 사업자 확인과 유료 운영 개방은 별도 조건입니다. | 심사 목표 2영업일 이내 |
| 5. 운영 연결 | 운영 시크릿·웹훅·회원 기능을 연결하고 구매·환불·만료 흐름을 확인합니다. 사용을 마친 테스트 키는 회수합니다. | 배포 설정 · 라이선스 관리 |
수동 전달·라이선스·앱 활성화 코드·다운로드·구독·API는 Free부터 쓸 수 있습니다. 사용량 측정과 화이트라벨 전달만 Business부터 열리며, 함께 고른 방식 중 조건에 못 미치는 것만 잠깁니다. (수동 전달은 연동이 필요 없어 이 가이드의 대상이 아닙니다.)
샌드박스 규칙
- 테스트 시크릿은
test_, 테스트 키는MF-TEST-…/mfk_test_…접두 — 육안으로 구분됩니다 - 테스트 시크릿 ↔ 테스트 키만 매칭 — 실 시크릿으로 테스트 키를 검증하거나 그 반대면 401로 거부됩니다
- verify 응답에
is_test: true가 포함되어 코드에서 분기할 수 있습니다 - 웹훅도 실 발급과 동일하게 발송됩니다 (payload에
is_test: true) — 웹훅 핸들러까지 미리 검증하세요 - 회수·재발급은 라이선스 관리에서 실 키와 동일한 콘솔로 처리합니다
테스트 키의 요금제 이름은
테스트로 고정됩니다. 주문 없이 발급되므로 구매한 요금제가 없기 때문입니다. 그래서 요금제 이름을 키로 찾는 설정은 테스트 키에서 그대로 매칭되지 않습니다:plan_features— 매칭이 안 되면plan_features: null이 되어 기능이 열리지 않습니다. JSON에"테스트"항목을 함께 넣어두면 개발 중에도 플랜 게이팅을 확인할 수 있습니다.sku_map— JSON에"테스트"항목이 있으면 테스트 키에도 같은 규칙으로 SKU가 실립니다. 없으면sku가null로 옵니다.
테스트·운영 시크릿 분리하기
.env.development / .env.production
# 개발 — 테스트 시크릿 (상품 수정 → 연동 정보 카드)
SAEMOL_SECRET=test_xxxxxxxxxxxxxxxx
# 운영 — 실 시크릿
SAEMOL_SECRET=xxxxxxxxxxxxxxxxactivate.ts — 개발·운영 공통 코드
import { Saemol } from '@saemol/sdk'
const saemol = new Saemol({ secret: process.env.SAEMOL_SECRET! })
const result = await saemol.verifyLicense({
licenseKey: inputKey,
deviceId: machineId(),
})
if (result.valid) {
activate()
if (result.is_test) console.info('[saemol] 샌드박스 키로 활성화됨')
}웹훅 로컬 테스트
권한 검증 결과의 짧은 캐시
키별로 검증 결과를 짧게 캐시하되 만료 시각을 넘기지 마세요. 회수·환불 웹훅을 받으면 캐시를 비웁니다. 검증 거부를 네트워크 장애로 처리해 기능을 열어서는 안 됩니다.
// 설명용: 마지막으로 검증한 키 하나만 캐시합니다.
let cached: { key: string; at: number; valid: boolean; expiresAt: number | null } | null = null
const TTL = 60_000
async function isLicensed(key: string): Promise<boolean> {
const now = Date.now()
if (cached?.key === key && now - cached.at < TTL &&
(cached.expiresAt === null || now < cached.expiresAt)) {
return cached.valid
}
cached = null
const result = await saemol.verifyLicense({ licenseKey: key })
const expiresAt = result.expires_at ? Date.parse(result.expires_at) : null
const valid = result.valid && (expiresAt === null || Date.now() < expiresAt)
cached = { key, at: Date.now(), valid, expiresAt }
return valid
}여러 키·디바이스를 캐시한다면 각각을 구분하고 보관 개수와 만료 정책을 정하세요. 통신 실패 시의 재시도·제한 화면도 별도로 구현해야 합니다. 오프라인 유예는 상품 정책에 맞춰 정하고, 명시적인 만료·회수를 우회하지 마세요.
출시 전 체크리스트
- 운영 배포의
SAEMOL_SECRET을 실 시크릿으로 교체했다 - 웹훅 수신 URL이 프로덕션 서버를 가리키고 서명 검증(
verifyWebhook)이 켜져 있다 - 디바이스 제한·허용 버전·업데이트 정보 등 상품 연동 설정을 확인했다
- 사용을 마친 테스트 키를 라이선스 관리에서 회수했다
- 심사를 신청했다 — 심사 기준 미리 확인
- 내 회원의 구매·환불·기간 만료에 따라 기능이 켜지고 꺼지는지 확인했다
- 유료 판매라면 사업자·정산 정보 확인과 새몰의 유료 운영 개방 여부를 확인했다