B2B SaaS 구독 해지 후 entitlement 회수 시점 검증 체크리스트 2026, access 종료 불일치 판별법

B2B SaaS 구독 해지 후 entitlement 회수 시점 검증 체크리스트 2026, access 종료 불일치 판별법


B2B SaaS에서 구독 해지 요청이 저장됐다고 해서 고객의 접근 권한이 바로 끝나는 것은 아닙니다. 고객이 현재 결제 기간 끝까지 사용할 수 있도록 예약 해지를 지원할 수도 있고, 즉시 해지·grace period·결제 실패에 따른 제한처럼 서로 다른 정책을 둘 수도 있습니다.

문제는 이 정책이 billing, subscription, 제품 권한, 캐시, CRM에 따로 구현될 때 생깁니다. cancellation_requested는 기록됐지만 실제 사용 권한은 계속 열려 있거나, 반대로 고객이 결제 기간 동안 사용할 수 있어야 하는데 너무 일찍 잠길 수 있습니다. 운영팀이 해지 수만 보면 이 차이를 찾기 어렵습니다.

이 글은 특정 결제 공급사의 해지 정책을 대신 결정하는 글이 아닙니다. B2B SaaS 운영팀이 구독 해지 뒤 다음 세 시각을 비교해 데이터·권한 전이의 누락을 찾는 체크리스트입니다.

이 글은 cancellation_reason, churn 분류, MRR·매출 인식, refund·proration 계산을 다루지 않습니다. cancel_at_period_end와 cancellation reason의 정의는 기존 취소·churn 글의 범위이며, 여기서는 해지 상태를 권한 시점 검증의 입력값으로만 사용합니다. feature flag rollout, plan upgrade, usage limit 변경도 entitlement 회수 지연으로 판정하지 않고 별도 권한·배포 상태로 분리합니다.

  • cancellation_effective_at: 해지가 실제로 효력을 갖는다고 정한 시각
  • access_end_at: 고객이 제품 기능에 접근할 수 없게 된 시각
  • entitlement_revoked_at: entitlement 원장에서 권한 회수가 기록된 시각

환불·credit note·proration의 금액 대사는 B2B SaaS 환불·크레딧 노트 추적 감사 체크리스트에서, 해지 사유와 churn 분류는 B2B SaaS 구독 취소·churn reason 추적 감사 체크리스트에서 다룹니다. 이 글에서는 금액이나 churn 분석을 다시 설명하지 않고 권한이 언제 닫혔는지 증명하는 시점 검증에 집중합니다.

먼저 해지 요청과 해지 효력을 분리합니다

운영 데이터에서 cancel_clicked_at, cancellation_requested_at, cancellation_effective_at을 하나의 cancelled_at으로 합치면 안 됩니다. 사용자가 해지 버튼을 누른 시각, 백엔드가 요청을 승인한 시각, 현재 서비스 접근이 끝나는 시각은 다를 수 있습니다.

필드 의미 흔한 오해
cancel_clicked_at 사용자가 화면에서 해지 동작을 시작한 시각 이 시각에 이미 해지가 확정됐다고 봄
cancellation_requested_at 서버가 해지 요청을 유효한 요청으로 저장한 시각 공급사 상태 변경 시각과 항상 같다고 봄
cancellation_effective_at 구독 해지가 실제 효력을 갖는 기준 시각 예약 해지와 즉시 해지를 구분하지 않음
access_end_at 제품 접근을 끝낼 예정 또는 실제 종료 시각 entitlement 회수 시각으로 대체함
entitlement_revoked_at 권한 원장에 회수가 기록된 시각 캐시가 갱신되기 전에도 실제 접근이 끝났다고 봄
access_observed_closed_at 실제 권한 확인 또는 차단 결과를 관찰한 시각 내부 명령 시각과 실제 제품 결과를 구분하지 않음

예를 들어 예약 해지라면 cancellation_requested_at은 오늘이고 cancellation_effective_ataccess_end_at은 현재 결제 기간의 종료 시각일 수 있습니다. 즉시 해지라면 세 시각이 가까울 수 있지만, 비동기 webhook과 권한 projection 때문에 entitlement_revoked_at이 늦어질 수 있습니다.

Stripe 공식 문서도 구독 취소에서 즉시 취소와 현재 기간 종료 시점에 취소하는 흐름을 구분합니다. 공급사 문서의 필드와 실제 계정 설정은 다를 수 있으므로, 외부 필드명을 내부 필드에 그대로 복사하기보다 내부에서 정책을 명시적으로 매핑해야 합니다.

원문: 원문기사 보기

원문: 원문기사 보기

검증 대상은 세 가지 상태 원장입니다

해지 후 접근 권한을 검증하려면 구독 상태 하나만 읽어서는 부족합니다. 아래 세 원장을 같은 account_id, subscription_id, entitlement_key로 연결해야 합니다.

1. Subscription 원장

구독 원장은 공급사 또는 내부 billing 시스템의 계약 상태를 저장합니다.

최소 필드는 다음과 같습니다.

필드 기록할 값 확인 질문
subscription_id 내부·공급사 구독 ID 동일 고객의 다른 구독과 혼동하지 않는가
account_id workspace 또는 회사 ID 개인 사용자와 회사 계정이 뒤섞이지 않는가
plan_code 내부 요금제 코드 현재 entitlement mapping 버전과 연결되는가
billing_status active, past_due, canceled 등 공급사 원문 상태를 임의로 번역하지 않았는가
cancel_mode immediate, period_end, grace, manual_review 해지 정책이 명시돼 있는가
cancellation_requested_at 요청 시각 고객 행동과 서버 확정을 구분하는가
cancellation_effective_at 효력 시각 period end 또는 즉시 시각의 근거가 있는가
source_event_id webhook 또는 내부 명령 ID 상태 변경 원문을 재조회할 수 있는가
state_version 상태 버전 또는 sequence 늦게 도착한 event가 최신 상태를 덮지 않는가

billing_status=canceled라는 값만으로 access_end_at을 계산하면 위험합니다. 어떤 제품은 해지 즉시 접근을 끝내고, 어떤 제품은 기간 종료까지 유지하며, 어떤 제품은 계약·지원 정책에 따라 별도 유예를 둡니다. 중요한 것은 어느 정책을 선택했는지가 아니라 선택한 정책과 실제 권한 전이가 같은지입니다.

2. Entitlement 원장

entitlement 원장은 고객이 실제로 사용할 수 있는 기능·좌석·용량을 표현합니다. 구독 원장과 동일한 시스템에 있더라도 별도 상태로 기록하는 편이 좋습니다.

필드 기록할 값 확인 질문
entitlement_id 권한 레코드 ID 한 계정의 여러 기능 권한을 구분하는가
entitlement_key api_access, sso, advanced_reports 기능명이 계획·제품과 일관되는가
expected_state 정책 계산 결과 왜 active 또는 revoked가 되어야 하는가
actual_state 권한 원장의 현재 상태 billing 결과와 실제 값이 일치하는가
entitlement_effective_at 권한 변경 적용 시각 명령 발행 시각과 구분되는가
entitlement_revoked_at 회수 기록 시각 회수 command 성공과 기록 완료를 혼동하지 않는가
source_action_id 권한 변경 action ID 어느 event가 권한을 바꿨는가
mapping_version plan-to-entitlement 변환 버전 과거 결과를 재현할 수 있는가
projection_observed_at read model 확인 시각 캐시·projection 지연을 분리하는가

예상 상태가 revoked라고 계산됐지만 실제 상태가 active라면 즉시 고객 차단부터 하지 말고 원인을 분류합니다. 권한 action이 생성되지 않았을 수도 있고, action은 성공했지만 projection이 늦었을 수도 있으며, period_end 정책상 아직 접근이 정상일 수도 있습니다.

3. Access 관찰 원장

권한 원장에서 revoked가 보인다고 실제 모든 요청이 차단됐다고 단정하지 않습니다. API gateway, 애플리케이션 cache, 세션 token, background job이 별도 상태를 가질 수 있습니다.

필드 기록할 값 확인 질문
access_end_at 정책상 접근 종료 예정·확정 시각 값의 출처가 정책 계산인지 관찰 결과인지
access_observed_closed_at 실제 차단 또는 deny 확인 시각 어떤 probe·요청으로 확인했는가
access_layer API, web, worker, export 등 일부 경로만 계속 열려 있지 않은가
cache_version 권한 cache 또는 policy version 오래된 cache가 남아 있지 않은가
session_revoked_at 세션·토큰 회수 시각 로그인 세션이 계속 유효하지 않은가
probe_result closed, still_open, unknown unknown을 closed로 자동 변환하지 않는가

특히 browser 화면에서 기능이 사라진 것과 API가 거부된 것은 같은 증거가 아닙니다. 사용자 화면, API, export, scheduled workflow처럼 유료 기능이 소비되는 경로를 나눠 확인해야 합니다.

세 시각을 한 행으로 대사합니다

실무에서는 해지 1건을 다음처럼 한 행으로 묶어 비교하면 시작하기 쉽습니다.

검증 키 cancellation effective access end entitlement revoked 판정
sub_demo_001 / api_access 2026-08-03 09:00 2026-08-03 09:00 2026-08-03 09:00 match
sub_demo_002 / reports 2026-08-03 09:00 2026-08-03 09:00 2026-08-03 09:07 delayed_revoke
sub_demo_003 / export 2026-08-03 09:00 2026-08-03 09:00 2026-08-02 18:00 early_revoke
sub_demo_004 / sso 2026-08-31 00:00 2026-08-31 00:00 null expected_until_effective

이 표에서 match는 값이 같은 경우만 뜻하지 않습니다. 해지 정책상 허용된 지연 범위와 검증 증거가 있어야 합니다. 예를 들어 period_end 정책으로 8월 31일까지 접근을 유지하기로 했다면 8월 3일에 entitlement_revoked_at=null인 것은 오류가 아닐 수 있습니다. 대신 access_end_at=2026-08-31과 그 정책의 근거가 기록되어 있어야 합니다.

권장 판정 코드

판정 코드는 원인을 한 단어로 숨기지 않도록 제한된 값으로 관리합니다.

판정 의미 다음 조치
match 정책상 예정 시각과 실제 권한 상태가 일치 종료 또는 표본 모니터링
expected_until_effective 아직 해지 효력 시각 전 효력 시각까지 관찰 예약
delayed_revoke 효력 시각 이후 권한 회수가 늦음 event·queue·projection owner 확인
early_revoke 효력 시각보다 먼저 권한이 닫힘 고객 영향과 정책 계산 확인
mapping_error plan 또는 feature mapping이 잘못됨 mapping 버전과 계약 기준 대조
stale_read 원장은 바뀌었지만 cache/read model이 오래됨 cache invalidation·재조회
unknown 필요한 근거를 조회하지 못함 자동 완료 금지, 수동 확인

unknownmatch로 바꾸는 fallback은 두지 않는 편이 좋습니다. 검증이 되지 않은 상태를 정상으로 집계하면 접근 권한 누락과 과다 허용을 모두 놓칠 수 있습니다.

확정된 해지 상태와 권한 회수는 별도로 확인합니다

구독 해지 상태가 billing 원장에 저장됐다는 로그는 권한 회수가 완료됐다는 증거가 아닙니다. 외부 event나 내부 명령으로 확정된 해지 상태를 입력으로 삼아, 권한 action 생성·권한 원장 반영·cache 갱신·실제 access 차단을 각각 남겨야 합니다. webhook 수신·signature·replay run 자체의 설계는 이 글의 범위가 아니며, 앞 단계가 확정한 상태와 시각만 사용합니다.

원문: 원문기사 보기

Paddle도 구독 취소를 별도 상태 변경으로 설명하지만, 이벤트 이름·전달 순서·재전송 동작은 공급사마다 다릅니다. 따라서 공급사 event를 내부 상태 전이의 유일한 시계로 쓰지 말고, state_version 또는 내부 sequence를 보존해야 합니다.

원문: 원문기사 보기

이벤트 처리 로그에서 최소한 아래 단계를 분리합니다.


confirmed_cancellation_state
  → cancellation_effective_computed
  → entitlement_revoke_action_created
  → entitlement_ledger_updated
  → cache_or_projection_updated
  → access_probe_closed

각 단계의 시각과 결과가 있어야 delayed_revokestale_read를 구분할 수 있습니다. processed=true, cancelled=true 같은 단일 플래그는 원인 조사에 부족합니다.


예약 해지·즉시 해지·유예를 분리합니다

같은 canceled 상태라도 고객 영향은 다릅니다. 내부 검증표에 cancel_mode를 반드시 포함하십시오.

예약 해지

예약 해지는 다음 갱신을 하지 않되 현재 기간 동안 기능을 유지하는 정책입니다. 이 경우 cancellation_requested_at은 오늘이지만 cancellation_effective_ataccess_end_at은 미래일 수 있습니다.

확인할 항목은 다음과 같습니다.

  • 현재 기간 종료 시각이 timezone 변환 뒤에도 같은가
  • 기간 종료 전에는 entitlement가 유지되는가
  • 기간 종료 event가 늦게 도착해도 이미 지난 기간을 다시 열지 않는가
  • 고객 화면의 “해지 예약” 상태와 API 접근 정책이 같은가
  • 만료 시점에 자동 회수 action과 audit log가 남는가

즉시 해지

즉시 해지는 cancellation_effective_at이 요청 시각과 가까울 수 있지만, 모든 기능이 동시에 닫힌다고 가정하면 안 됩니다. API와 web은 다른 cache를 사용할 수 있고, 이미 발행된 export나 background job은 별도 처리 정책이 필요할 수 있습니다.

확인할 항목은 다음과 같습니다.

  • 권한 회수 action이 한 번만 생성되는가
  • 이미 처리한 webhook이 다시 도착해 권한을 재부여하지 않는가
  • active session과 API token이 정책대로 처리되는가
  • access probe가 기능별로 모두 deny를 확인하는가
  • 재활성화 시 이전 회수 event가 새 권한을 덮지 않는가

유예 또는 결제 실패 후 제한

결제 실패나 grace period는 해지와 같은 상태가 아닐 수 있습니다. B2B SaaS 결제 실패·dunning 자동화 체크리스트와 연결해 billing_status, entitlement_status, access_status를 별도 필드로 보십시오.

이 글에서 유예 기간의 길이나 접근 제한 정책을 정하지 않는 이유는 제품·계약·공급사 설정에 따라 다르기 때문입니다. 운영팀이 해야 할 일은 정책을 문서화하고, 정한 시각과 실제 권한 상태가 일치하는지 검증하는 것입니다.

대사 리포트 템플릿

아래 필드는 스프레드시트나 운영 대시보드의 최소 열로 사용할 수 있습니다. 실제 고객 이메일, 카드 정보, 계약 원문은 넣지 말고 내부 ID와 마스킹된 식별자만 사용합니다.

영역 필드 예시 판정에 쓰는 이유
식별 account_id / subscription_id acct_demo / sub_demo_002 고객·구독 연결
정책 cancel_mode immediate 종료 기준 선택
정책 cancellation_effective_at 2026-08-03T09:00:00Z 권한 종료 기대 시각
권한 entitlement_key reports 기능 범위
권한 entitlement_revoked_at 2026-08-03T09:07:12Z 원장 회수 완료 시각
접근 access_end_at 2026-08-03T09:00:00Z 제품 접근 종료 기준
관찰 access_observed_closed_at 2026-08-03T09:08:01Z 실제 차단 관찰
근거 source_event_id / action_id evt_demo / act_demo 상태 전이 추적
버전 state_version / mapping_version 42 / ent-v7 늦은 event·mapping 오류 확인
결과 reconciliation_status delayed_revoke 후속 조치 라우팅
담당 owner / next_review_at billing-ops / timestamp 미해결 행 관리

공개 전후로 확인할 내부 링크

이 글은 기존 글의 내용을 반복하기보다 권한 회수 시점에 초점을 둡니다. 다음 링크는 각 시스템의 경계를 이해하는 용도로만 연결합니다.

직전 replay 대사 글은 event ledger와 provider object를 연결하는 방법을 설명하지만, 이 글은 그 결과로 확정된 cancellation 시각을 실제 접근 권한 종료 시각과 대조하는 후속 검증으로 범위를 좁힙니다. webhook replay와 event ledger·provider object·invoice 대사는 기존 replay 글의 범위이며, 여기서는 확정된 cancellation 시각 이후의 내부 access·entitlement 시각만 대조합니다.

최종 체크리스트

공개 또는 운영 대시보드 반영 전에 아래 질문에 모두 답할 수 있어야 합니다.

  • 해지 요청 시각과 해지 효력 시각이 별도 필드로 저장되는가
  • cancel_mode가 immediate, period_end, grace 등으로 명시돼 있는가
  • access_end_at을 누가 계산했고 어떤 정책 버전이 근거인가
  • entitlement_revoked_at이 action 생성 시각이 아니라 원장 반영 시각인가
  • API, web, export, workflow 등 주요 access layer를 나눠 확인했는가
  • webhook 수신 성공과 권한 회수 완료를 별도 상태로 집계하는가
  • 늦은 event나 재활성화가 이전 해지 상태를 덮지 않는가
  • delayed_revoke, early_revoke, stale_read, unknown을 별도 판정하는가
  • 검증 실패 행에 담당자와 다음 확인 시각이 있는가
  • 실제 고객의 개인정보·결제정보·계약 원문을 리포트에 넣지 않았는가

핵심은 해지 버튼이나 billing 상태가 아니라 고객이 언제까지 무엇을 사용할 수 있었고, 언제부터 무엇이 닫혔는지 재현할 수 있는가입니다. cancellation_effective_at, access_end_at, entitlement_revoked_at을 분리하면 예약 해지의 정상 유지와 실제 회수 지연을 같은 오류로 집계하지 않을 수 있습니다.