베타 운영 안내·현재 베타로 운영하며, 유료 결제와 관련 기능은 준비가 끝나는 대로 순차적으로 연동합니다.운영 안내

개발 단계부터 연동

테스트 키로 권한 검증과 웹훅 수신을 먼저 연결하세요. 실제 유료 판매에는 사업자·정산 정보 확인, 새몰의 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=xxxxxxxxxxxxxxxx
activate.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] 샌드박스 키로 활성화됨')
}

웹훅 로컬 테스트

테스트 키 발급 시에도 실 발급과 동일한 웹훅이 발송됩니다 (payload에 is_test: true). 다만 localhost는 외부에서 접근할 수 없으므로 로컬 개발 중에는:

  • ngrok·cloudflared 같은 터널링 도구로 로컬 서버를 임시 공개 URL로 노출하고, 그 URL을 웹훅 수신 URL로 등록하세요
  • 놓친 발송은 연동 관리에서 실패 내역 확인 후 수동 재발송할 수 있습니다
  • 핸들러에서 data.is_test로 분기하면 테스트 발급이 운영 데이터에 섞이지 않습니다

서명 검증·이벤트 목록은 웹훅 가이드를 보세요.

권한 검증 결과의 짧은 캐시

키별로 검증 결과를 짧게 캐시하되 만료 시각을 넘기지 마세요. 회수·환불 웹훅을 받으면 캐시를 비웁니다. 검증 거부를 네트워크 장애로 처리해 기능을 열어서는 안 됩니다.

// 설명용: 마지막으로 검증한 키 하나만 캐시합니다.
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)이 켜져 있다
  • 디바이스 제한·허용 버전·업데이트 정보 등 상품 연동 설정을 확인했다
  • 사용을 마친 테스트 키를 라이선스 관리에서 회수했다
  • 심사를 신청했다 — 심사 기준 미리 확인
  • 내 회원의 구매·환불·기간 만료에 따라 기능이 켜지고 꺼지는지 확인했다
  • 유료 판매라면 사업자·정산 정보 확인과 새몰의 유료 운영 개방 여부를 확인했다