Skip to content
스토브
마지막 업데이트

실제 적용 흐름이 궁금하신가요?

이용 시나리오 / 캡챠 이용하기

캡챠

이해하기


캡챠(CAPTCHA)는 자동화된 봇·매크로의 비정상 접근을 차단하기 위한 자동 입력 방지 기능이에요.
스토브에서는 두 종류의 캡챠를 제공해요.

구분 플랫폼 캡챠 인게임 캡챠 (CP사 직접 구현)
적용 영역 회원가입·로그인·비밀번호 변경 등 플랫폼 인증 동선 게임 서버 진입, 아이템 거래, 재화 획득 등 인게임 영역
제공 형태 플랫폼이 자체 판단으로 자동 노출
(의심 IP, 비밀번호 5회 오입력 등)
스토브가 캡챠 API만 제공
(노출 조건·시점·대상은 CP사가 자율 설계)
캡챠 타입 텍스트 입력 / 퍼즐 (Slide, Click, Drag&drop, Rotate) 텍스트 입력 캡챠
CP사 작업 별도 연동 불필요 필요 (API 호출 + 캡챠 UI 노출 처리)

이 가이드는 인게임 캡챠 연동을 다뤄요
스토브 플랫폼 캡챠는 별도 연동 작업이 없어요.

활용 방법

인게임 캡챠는 자동화된 봇·매크로 계정이 게임 시스템을 어뷰징하는 것을 차단할 때 활용해요.
노출 대상을 전체 이용자로 할지, 특정 조건을 만족하는 이용자만으로 할지는 CP사가 자율적으로 설계할 수 있어요.

활용 시나리오 노출 시점 설명
자동화 봇 계정 차단 게임 서버 선택 시점 대량 봇 계정의 동시 게임 진입 차단에 사용
캡챠 통과 시 일정 기간(예: 1주) 동일 이용자에게는 미노출 처리해 일반 이용자 부담 감소 가능
경제 시스템 어뷰징 탐지 특정 행위 임계치 초과 시 아이템 거래·재화 획득 등 특정 행위가 단시간 내 N회 발생 시 캡챠 노출
매크로 이용자 식별과 차단에 효과적
민감 행위 직전 검증 결제·중요 거래 직전 고액 거래, 계정 양도, 자동 매매 의심 행위 직전에 노출해 부정 사용 방지

동작 원리

캡챠 리소스 요청·검증 API는 모두 게임 서버에서 호출해야 해요.
게임 서버가 중간에서 노출 조건을 직접 컨트롤하는 구조예요.

구성 요소 역할
게임 클라이언트 캡챠 리소스(이미지)를 화면에 노출하고, 이용자가 입력한 캡챠 값을 게임 서버로 전달
게임 서버 캡챠 노출 조건을 판단하고, 스토브 캡챠 서버에 캡챠 리소스 요청·검증 API 호출 (API 호출 시 API Access Token 필요)
스토브 캡챠 서버 캡챠 이미지·정답 키를 발급하고, 게임 서버가 전달한 이용자 입력 값 검증

연동 가이드


연동 준비

캡챠 연동에 앞서 크게 세 영역의 준비가 필요해요.

  • 인증: 스토브 캡챠 API 호출용 인증 토큰(API Access Token) 발급
  • 노출 정책 설계: 노출 기간·캡챠 레벨·노출 빈도·노출 조건 사전 정의 (아래 캡챠 노출 정책 설계 참조)
  • 운영 도구 구성: 정책 값을 클라이언트 패치 없이 동적 변경할 수 있도록 자체 운영툴 구성 (권장)

캡챠 노출 정책 설계

운영 중 캡챠 강도를 유연하게 조정하려면, 자체 운영툴에서 아래 항목을 관리하고 게임 서버가 호출 시 참조하는 구조를 권장해요.

설정 항목 설명 예시
노출 기간 캡챠를 노출할 운영 기간 상시 운영 / 특정 일시 구간
캡챠 레벨 캡챠의 난이도. captcha_level 파라미터로 전달 (숫자가 높을수록 난이도 상승) 정적 이미지(PNG) 타입 1~7 / 애니메이션 이미지(GIF) 타입 101~107
노출 빈도 기간 내 동일 이용자에게 노출하는 횟수 접속 시마다 / 기간 내 1회 / 기간 내 일 1회
노출 조건 캡챠를 트리거하는 조건 서버 선택 시 / 캡챠 통과 시 1주간 미노출 / 거래 N회 초과 시

난이도를 무작정 올리면 일반 이용자 피해가 커져요
공격 패턴이 관찰될 때 캡챠 레벨만 높이면 정상 이용자의 캡챠 통과율이 떨어질 수 있어요.
임계치 기반 노출, 통과 후 1주간 미노출 같은 정책과 함께 운영해 주세요.

처리 흐름

캡챠 적용은 리소스 요청 → 검증 두 단계 흐름이에요.
게임 서버가 중간에서 노출 조건을 판단하고, 스토브 캡챠 서버에 두 API를 순서대로 호출하는 구조예요.


게임 서버는 다음 3단계를 순차로 수행해요.

  1. 노출 조건 판단 — 자체 운영툴 정책(노출 기간·레벨·빈도·조건)과 통과 이력(마지막 통과 시각 등)을 검사해 캡챠 노출 여부를 결정해요.
  2. 캡챠 리소스 요청 — 노출이 필요하면 스토브 캡챠 서버에서 Captcha Key와 캡챠 이미지를 발급받아 클라이언트에 전달해요.
  3. 캡챠 검증 — 이용자가 입력한 값을 스토브 캡챠 서버에 전달해 유효성을 확인하고, 결과에 따라 원래 액션 진행·재시도·차단으로 분기해요.

개발하기


1. 사전 준비

캡챠 연동 코드를 작성하기 전에 갖춰져야 할 선행 조건이에요. 캡챠 기능은 SDK 의존성 없이 게임 서버에서만 호출하는 Server-to-Server API로 동작해요.

항목 내용 비고
API Access Token 발급 캡챠 API 호출용 인증 토큰. 환경별(Live, Sandbox) 각각 발급 필요 퍼블리싱 기술 담당자 요청
호출 주체 확인 모든 캡챠 API는 게임 서버에서만 호출 (Server-to-Server).
클라이언트 직접 호출 시 API Access Token이 유출되며 캡챠 검증 우회가 가능하므로 금지
어뷰징 방지
Caller-ID 정의 API 호출자(게임 서버) 식별 헤더. EXT-SERVER-{게임명} 형식으로 퍼블리싱 기술 담당자와 사전 협의해 정의 필수
Caller-Detail (이용자 식별자) 정의 API 호출자 측 이용자 식별 헤더.
UUID·CUID·캐릭터 ID 중 정책에 맞게 선택해 캡챠 API 호출 시 헤더로 전달
권장
클라이언트 IP 추출 차단 IP 식별과 IP별 Rate Limit 적용에 활용.
게임 서버에서 올바른 클라이언트 IP를 추출하여 캡챠 API 호출 시 전달 필요
필수
노출 정책 로직 노출 기간·레벨·빈도·조건은 자체 운영툴에서 관리하고, 게임 서버는 호출 시점에 정책을 참조해 캡챠 노출 여부와 통과 이력을 판단 권장
자체 운영툴 캡챠 레벨·빈도·조건을 클라이언트 패치 없이 동적 변경 가능하도록 운영툴 구성 권장

2. 게임 서버 구현


캡챠 노출 여부 판단

자체 운영툴 정책(노출 기간·레벨·빈도·조건)과 통과 이력을 검사해 캡챠 노출 여부를 결정해요. 노출 이력은 스토브에서 관리하지 않으므로, 게임 서버에서 직접 저장·관리해요.

  • 운영툴 정책 값을 참조해 캡챠 노출 여부 판단
  • 이용자별 마지막 캡챠 통과 시각 저장 및 노출 빈도 정책 반영
  • 임계치 기반 노출 시 행위 카운트 누적 및 통과 시 초기화 로직 구현
  • 노출 이력 관리는 게임 서버 책임 (스토브 미관리)



캡챠 리소스 요청·검증 API 연동

캡챠 API는 리소스 요청과 검증 두 가지로 구성돼요. 두 API 모두 게임 서버에서만 호출해요.

API Method Path 요청 파라미터 응답 주요 필드
캡챠 리소스 요청 GET /blockchecker/v1.0/server/captcha Query: captcha_level, client_ip code, value.captcha_key, value.resource.image_url
캡챠 검증 POST /blockchecker/v1.0/server/verify Body: captcha_level, captcha_key, captcha_value, client_ip code

공통 헤더는 다음과 같아요.

  • Authorization: Bearer <API Access Token>

  • Caller-ID: EXT-SERVER-{게임명}

  • Caller-Detail: {이용자 식별자}

  • 검증 API는 추가로 Content-Type: application/json 헤더 필요

⚠️ 주의: captcha_key 사용 규칙
캡챠 리소스 요청 API로 받은 captcha_key는 다음 조건을 모두 만족할 때만 유효해요.

  • 유효 기간 2분(120초) — 발급 시점부터 120초가 지나면 자동 만료돼요.
  • 성공 검증 1회 한정 — 검증에 성공하면 즉시 무효화되어 재사용할 수 없어요.
  • 누적 실패 3회까지 허용 — 값 불일치(49702)는 동일 키로 다시 입력할 수 있지만, 4회째 실패하면 키가 무효화돼요.

위 조건 중 하나라도 초과되면 49701이 반환되며, 새 리소스 요청 API를 다시 호출해야 해요.



응답 코드별 분기 처리

응답은 HTTP 200 또는 401로 내려오며, 두 API 모두 code 0이 정상이고 200 응답에서도 code != 0이면 비즈니스 오류로 분기해야 해요. 명세 외 HTTP 4xx/5xx·네트워크 예외는 스토브 측 일시 장애 가능성이 있으므로, 재시도·로깅·알람 정책을 함께 설계해요. 응답 코드 값을 이용자에게 직접 노출하지 말고, 상황에 맞는 안내 UI로 분기 처리해요.

주요 코드별 후속 처리:

  • 검증 성공 (0) — 캡챠 레이어를 닫고 원래 액션(게임 진입·거래·민감 행위 등) 진행. 통과 시각을 게임 서버에 저장해 다음 노출 빈도·임계치 판단에 활용
  • 값 불일치 (49702) — 동일 captcha_key로 재입력 UI 노출. 단, 3회까지 허용되며 4회째 실패 시 49701로 전환되므로 새 리소스 발급 분기와 함께 처리
  • Key 만료·누적 실패 초과·이미 사용된 Key (49701) — 캡챠 리소스 요청 API로 새 리소스를 발급받아 다시 노출. 동일 Key 재시도는 의미 없음
  • 차단 IP (49500, 리소스 요청·검증 양쪽 모두에서 발생 가능) — 재시도 없이 접속 차단 안내 노출
  • 토큰 오류 (40101~40104, 49318) — API Access Token 설정 문제. 이용자에게는 일반 안내, 운영팀에 알림 처리
  • 파라미터 오류 (49200, 49240, 49241, 49700, 49713) — 게임 서버 호출 파라미터 누락·형식 오류. 이용자에게는 일반 안내, 운영팀에 알림 처리
  • 서버 내부 오류 (49106) — 스토브 서버 측 일시 오류. 재시도 후에도 지속되면 운영팀에 알림 처리



시퀀스 다이어그램

정상 흐름 (Happy Path)


에러 처리 흐름 (Error Handling)


각 응답 코드의 상세 의미와 권장 처리는 위 "응답 코드별 분기 처리 구현" 항목을 참고해 주세요.



샘플 코드

본 예시는 게임 서버가 직접 운영하는 영역의 참고 구현이에요. 실제 환경에 맞게 의존성·예외 처리·로깅을 보완해 사용해 주세요. 본문 §"응답 코드별 분기 처리 구현"의 코드 분류와 동일한 정책을 적용해, 토큰 오류는 TokenAuthException·차단 IP는 BlockedIpException으로 throw해 컨트롤러 레이어에서 일괄 처리하는 구조를 권장해요. 샘플 코드는 Spring Boot 3.2+ (Spring Framework 6.1+) 환경에서 RestClient를 사용하는 기준이에요. 일시 장애 대응을 위한 재시도 로직(49106·HTTP 5xx·네트워크 예외 등)은 본 샘플에 포함되어 있지 않아요. Spring Retry·Resilience4j 같은 라이브러리를 활용해 별도 레이어에서 지수 백오프 기반으로 구현하는 것을 권장해요.


1) 캡챠 리소스 요청
java
@Component
public class StoveCaptchaClient {

    private static final String HOST_LIVE    = "https://api.onstove.com";
    private static final String HOST_SANDBOX = "https://api.gate8.com";

    private final RestClient restClient;

    @Value("${stove.api.access-token}") private String apiAccessToken;
    @Value("${stove.caller-id}")        private String callerId;
    @Value("${stove.profile:live}")     private String profile;

    public StoveCaptchaClient(RestClient.Builder builder) {
        this.restClient = builder.build();
    }

    /** 캡챠 리소스 요청. 차단 IP는 BlockedIpException, 토큰 오류는 TokenAuthException으로 throw. */
    public CaptchaResource requestCaptcha(int level, String clientIp, String callerDetail) {
        String host = "sandbox".equalsIgnoreCase(profile) ? HOST_SANDBOX : HOST_LIVE;

        CaptchaResponse res = restClient.get()
                .uri(host + "/blockchecker/v1.0/server/captcha?captcha_level={lvl}&client_ip={ip}",
                     level, clientIp)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiAccessToken)
                .header("Caller-ID", callerId)
                .header("Caller-Detail", callerDetail)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError, (req, r) -> {
                    // 40101~40104 → HTTP 401 토큰 오류
                    throw new TokenAuthException("HTTP " + r.getStatusCode());
                })
                .body(CaptchaResponse.class);

        if (res == null) throw new IllegalStateException("empty captcha response");
        switch (res.code) {
            case 0:     return res.value;
            case 49500: throw new BlockedIpException("49500");                                         // 차단 IP
            case 49318: throw new TokenAuthException("code=" + res.code + ", message=" + res.message); // 토큰 오류 (본문 응답)
            case 49106:                                                                                 // 서버 내부 오류
                // log.error("STOVE captcha server error: code={}, message={}", res.code, res.message);
                throw new IllegalStateException("server internal: code=" + res.code + ", message=" + res.message);
            case 49240: case 49241: case 49713:                                                         // 파라미터 오류
                // log.error("STOVE captcha param error: code={}, message={}", res.code, res.message);
                throw new IllegalStateException("param error: code=" + res.code + ", message=" + res.message);
            default:
                // log.error("STOVE captcha unknown error: code={}, message={}", res.code, res.message);
                throw new IllegalStateException("captcha request failed: code=" + res.code + ", message=" + res.message);
        }
    }

    public static class BlockedIpException extends RuntimeException {
        public BlockedIpException(String code) { super("blocked IP, code=" + code); }
    }

    public static class TokenAuthException extends RuntimeException {
        public TokenAuthException(String detail) { super("captcha token error: " + detail); }
    }

    @Getter @Setter
    public static class CaptchaResponse {
        public int code;
        public String message;
        public CaptchaResource value;
    }

    @Getter @Setter
    public static class CaptchaResource {
        @JsonProperty("captcha_key")  public String captchaKey;
        @JsonProperty("captcha_type") public String captchaType;
        public Resource resource;
    }

    @Getter @Setter
    public static class Resource {
        @JsonProperty("image_url") public String imageUrl;
    }
}



2) 캡챠 검증
java
public VerifyResult verifyCaptcha(int level, String key, String value, String clientIp, String callerDetail) {
    String host = "sandbox".equalsIgnoreCase(profile) ? HOST_SANDBOX : HOST_LIVE;

    Map<String, Object> body = Map.of(
        "captcha_level", level,
        "captcha_key",   key,
        "captcha_value", value,
        "client_ip",     clientIp
    );

    VerifyResponse res = restClient.post()
            .uri(host + "/blockchecker/v1.0/server/verify")
            .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiAccessToken)
            .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .header("Caller-ID", callerId)
            .header("Caller-Detail", callerDetail)
            .body(body)
            .retrieve()
            .onStatus(HttpStatusCode::is4xxClientError, (req, r) -> {
                // 40101~40104 → HTTP 401 토큰 오류
                throw new TokenAuthException("HTTP " + r.getStatusCode());
            })
            .body(VerifyResponse.class);

    if (res == null) throw new IllegalStateException("empty verify response");
    switch (res.code) {
        case 0:     return VerifyResult.PASS;          // 검증 성공 → 원래 액션 진행
        case 49702: return VerifyResult.RETRY;         // 값 불일치 → 동일 키로 재입력
        case 49701: return VerifyResult.NEW_RESOURCE;  // Key 무효/만료 → 새 리소스 요청
        case 49500: throw new BlockedIpException("49500");                                         // 차단 IP
        case 49318: throw new TokenAuthException("code=" + res.code + ", message=" + res.message); // 토큰 오류 (본문 응답)
        case 49200: case 49240: case 49241: case 49700: case 49713:                                 // 파라미터 오류
            // log.error("STOVE captcha verify param error: code={}, message={}", res.code, res.message);
            throw new IllegalStateException("param error: code=" + res.code + ", message=" + res.message);
        default:
            // log.error("STOVE captcha verify unknown error: code={}, message={}", res.code, res.message);
            throw new IllegalStateException("verify failed: code=" + res.code + ", message=" + res.message);
    }
}

public enum VerifyResult { PASS, RETRY, NEW_RESOURCE }

@Getter @Setter
public static class VerifyResponse {
    public int code;
    public String message;
}



3) 요청 → 검증 전체 흐름
java
@Service
public class GameServerCaptchaFlow {
    private final StoveCaptchaClient captchaClient;
    private final CaptchaSessionStore sessionStore;   // 게임사 자체 운영툴·세션 관리

    /** 노출 조건 충족 시 캡챠 발급 후 클라이언트에 전달 */
    public CaptchaIssued issueCaptcha(String userId, String clientIp) {
        int level = sessionStore.resolveLevel(userId);
        CaptchaResource res = captchaClient.requestCaptcha(level, clientIp, userId);
        sessionStore.bindKey(userId, res.captchaKey, level);          // 사용자-키 매핑 저장
        return new CaptchaIssued(res.captchaKey, res.captchaType, res.resource.imageUrl);
        // 차단 IP는 BlockedIpException으로 throw되므로 컨트롤러에서 차단 UI 응답 처리
    }

    /** 클라이언트가 입력값 전달 시 검증 후 후속 처리 */
    public VerifyResult verify(String userId, String userInput, String clientIp) {
        CaptchaSessionStore.Binding binding = sessionStore.lookup(userId)
            .orElseThrow(() -> new IllegalStateException("no captcha issued"));

        VerifyResult result = captchaClient.verifyCaptcha(
            binding.level, binding.captchaKey, userInput, clientIp, userId
        );

        if (result == VerifyResult.PASS) {
            sessionStore.markPassed(userId);   // 통과 시각 갱신
            sessionStore.clear(userId);
        } else if (result == VerifyResult.NEW_RESOURCE) {
            sessionStore.clear(userId);        // 키 만료 → 새 리소스 요청 유도
        }
        return result;
        // 차단 IP/토큰 오류는 컨트롤러에서 try-catch로 일괄 처리 권장
    }

    public record CaptchaIssued(String captchaKey, String captchaType, String imageUrl) {}
}

3. 게임 클라이언트 구현


캡챠 UI

게임 서버에서 발급받은 캡챠 이미지를 화면에 노출하고, 이용자가 입력한 값을 다시 게임 서버로 전달해요. 캡챠 UI 디자인은 게임이 자체적으로 구현해요.

  • 게임 서버에서 받은 캡챠 이미지(image_url)와 Captcha Key(captcha_key)를 화면에 노출
  • 이용자 입력 값과 Captcha Key를 다시 게임 서버로 전달하는 동선 구현
  • 캡챠 노출 UI는 게임이 자체 디자인·구현

리소스 사양 및 타입 분기

  • image_url로 제공되는 캡챠 이미지의 크기는 240 × 80 (PNG 또는 GIF)이에요. UI 레이아웃·배율 설계 시 이 사이즈를 기준으로 입력 영역·여백을 잡아요.
  • 게임 서버는 캡챠 리소스 응답으로 받은 captcha_type을 클라이언트에 함께 전달해, 클라이언트가 타입별 렌더링을 분기할 수 있도록 제공해요.
    • image — 정적 PNG 이미지
    • animated_image — 애니메이션 GIF 이미지

▲ CP사 자체 구현 캡챠 UI 예시 (로드나인)



검증 결과별 클라이언트 분기 처리

게임 서버에서 내려준 검증 결과에 따라 클라이언트 UI를 분기 처리해요. 응답 코드 값을 이용자에게 직접 노출하지 말고, 상황에 맞는 안내 UI로 분기해요.


입력값 전처리 권장

  • 이용자 입력값의 좌우 공백은 trim 후 전달해요.
  • 캡챠 검증은 대소문자를 구분하지 않아요. 이용자 입력 편의를 위해 클라이언트가 대문자(또는 소문자) 표시·입력으로 통일해도 결과에 영향이 없어요.

검증 결과별 UI 분기

클라이언트는 스토브 캡챠 서버의 응답 코드를 직접 받지 않아요. 게임 서버가 스토브 응답을 자체 상태/코드로 변환해 클라이언트에 전달하는 것을 전제로 해요. 아래 분기는 응답 코드가 아닌 의미(상태) 기준이며, CP사가 자체 인터페이스 규약으로 상태를 정의해 매핑하면 돼요.

  • 검증 성공 — 캡챠 레이어를 닫고 원래 액션 진행
  • 값 불일치 — 동일 캡챠 이미지를 유지한 채 재입력 UI 노출이 기본 동작이에요. 동일 캡챠로 3회까지 재시도할 수 있으며, CP사 자체 정책에 따라 1회 실패 시마다 새로고침 처리(매번 새 리소스 요청) 도 가능해요.
  • Captcha Key 만료/무효 — 새 리소스를 받아 캡챠 이미지를 갱신 후 다시 노출
  • 차단된 IP — 재시도 없이 차단 안내 얼럿 노출
  • 그 외 오류 — 일반 오류 안내. 재시도 동선은 운영 정책에 맞춰 설계

상황별 UI 분기 요약

검증 결과 상태 UI 동작 새 리소스 요청 재시도
검증 성공 캡챠 레이어 닫고 원래 액션 진행 불필요
값 불일치 동일 이미지 유지 + 재입력 안내
(자체 정책에 따라 매번 새로고침 가능)
선택 (정책에 따름) 가능 (3회까지)
Key 만료/무효 새 이미지로 갱신 후 다시 노출 필수 새 키로 재시작
차단된 IP 차단 안내 얼럿 금지 없음
그 외 오류 일반 오류 안내 정책에 따름 정책에 따름

위 상태는 스토브 응답 코드와 다음과 같이 매핑돼요(게임 서버 변환 기준): 검증 성공 ← 0, 값 불일치 ← 49702, Key 만료/무효 ← 49701, 차단된 IP ← 49500. 게임 서버에서의 코드 처리 상세는 §3 "응답 코드별 분기 처리 구현"을 참고해 주세요.



다국어·접근성 처리

글로벌 게임에서는 캡챠 UI 안내 문구를 게임 지원 언어로 다국어 처리해요. 이용자가 캡챠 입력 중 실수를 정정할 수 있도록 보조 동작 버튼도 함께 제공해요.

  • 캡챠 UI 안내 문구를 게임 지원 언어로 다국어 처리
  • 새로 고침·재입력·취소 등 보조 동작 버튼 제공
  • 글로벌 게임은 필수 검토 항목

4. 운영 가이드

운영 단계에서 캡챠 장애 상황에 대응하기 위해, 재시도 전략과 장애 시 fallback 정책을 사전에 설계해요.


권장 재시도 전략
  • 재시도 대상: 일시적 HTTP 5xx, 네트워크 예외, code 49106 (서버 내부 오류)
  • 재시도 정책: 지수 백오프(예: 200ms → 500ms) 권장

스토브 캡챠 서버 일시 장애 시 Fallback 정책

스토브 캡챠 서버가 일시 장애 상태이거나 응답 지연이 지속되는 경우, 캡챠 검증을 통과 처리(bypass) 하는 정책을 권장해요.

  • 일정 시간 내 HTTP 5xx·타임아웃이 임계치를 초과하면 fallback 모드로 자동 전환
  • Fallback 동안 운영팀이 즉시 인지할 수 있도록 로그·알람 발생
  • 스토브 캡챠 서버 회복 후 fallback 모드 자동 해제 또는 운영 판단에 따른 수동 해제

운영 알람

다음 이벤트는 즉시 운영팀에 알람을 발생시키도록 설계해요.

  • 토큰 오류 (40101~40104, 49318) — 토큰 만료 후 재발급 직전 시점에 일시적으로 발생하는 경우는 정상 운영 환경에서도 나타날 수 있으나, 지속 발생 시 API Access Token 설정 점검 필요
  • 파라미터 오류 (49200, 49240, 49241, 49700, 49713) — 게임 서버 호출 측 파라미터 누락·형식 오류. 코드 점검 필요

자주 묻는 질문



Q. 스토브 플랫폼 캡챠도 별도로 연동해야 하나요?
A. 아니요, 별도 연동은 필요하지 않아요. 회원가입·로그인·계정찾기 등 플랫폼 인증 동선에서 의심 IP·비밀번호 5회 오입력 등 위험 감지 시 스토브가 자동으로 캡챠를 노출해요.
이 가이드에서 다루는 인게임 캡챠는 스토브가 API만 제공하므로, CP사가 직접 연동해야 해요.
Q. 캡챠 노출 대상을 전체 이용자로 해야 하나요?
A. 아니요, CP사가 자율적으로 설계해요. 전체 이용자 대상으로 운영할 수도 있고, 특정 조건(예: 단시간 내 N회 행위 발생)을 만족하는 이용자에게만 노출할 수도 있어요.
일반 이용자 부담을 줄이려면 임계치 기반 노출과 캡챠 통과 후 일정 기간 미노출 정책을 함께 운영해 주세요.
Q. 캡챠 통과 후 일정 기간 미노출 처리는 어디서 관리하나요?
A. CP사 자체 운영툴이나 게임 서버에서 관리해야 해요. 스토브 캡챠 서버는 단순히 리소스 발급과 검증만 처리해요.
이용자별 마지막 통과 시각을 저장하고, API 호출 직전에 게임 서버가 미노출 여부를 판단하는 구조를 권장해요.
Q. 캡챠 API를 게임 클라이언트에서 직접 호출해도 되나요?
A. 안 돼요. 캡챠 리소스 요청 API와 검증 API는 모두 게임 서버에서만 호출해야 해요.
클라이언트에서 직접 호출하면 API Access Token이 노출될 수 있고, 외부 공격자가 캡챠를 우회·조작할 수 있어요.
Q. Caller-ID와 Caller-Detail은 어떤 값을 입력해야 하나요?
A. Caller-ID는 호출자(게임 서버) 식별 정보로 EXT-SERVER-{게임명} 형식으로 입력해요. (예: EXT-SERVER-LORDNINE)
Caller-Detail은 클라이언트(이용자) 식별 정보로 UUID나 이용자 ID(CUID 또는 게임 내 캐릭터 ID)를 입력해요.
Q. captcha_level 값은 어떻게 정하면 좋을까요?
A. 캡챠 타입별 범위 중에서 선택해요. 정적 이미지(PNG)는 1 ~ 7, 애니메이션 이미지(GIF)는 101 ~ 107이며, 숫자가 클수록 난이도가 높아요.
일반 동선에는 낮은 레벨을 권장하고, 의심 행위가 누적된 이용자에게는 높은 레벨을 적용하는 식으로 단계 운영해요.
난이도를 무작정 올리면 정상 이용자의 캡챠 통과율이 함께 떨어져 이탈로 이어질 수 있으므로, 임계치 기반 노출 조건과 함께 설계해요.
Q. 캡챠 검증 시 이용자 입력값의 대소문자를 구분하나요?
A. 아니요, 대소문자를 구분하지 않아요. 예를 들어 정답이 6a537Y인 캡챠에 이용자가 6A537y로 입력해도 정상 통과돼요.
따라서 클라이언트가 입력 편의를 위해 입력값을 대문자(또는 소문자)로 강제 변환·표시해도 검증 결과에 영향이 없어요. Caps Lock 상태나 입력기 자동 변환으로 인한 문의를 줄이는 데 활용할 수 있어요.
단, 좌우 공백·줄바꿈 등은 trim 후 전달하는 것을 권장해요.
Q. 49702(Captcha 값 불일치) 발생 시 어떻게 처리해야 하나요?
A. 캡챠 재입력 UI를 노출해요. 동일한 Captcha Key를 그대로 사용해 재시도할 수 있어요.
다만 일정 횟수 이상 실패하면 새 캡챠 리소스를 요청하거나 접속을 차단하는 등의 후속 정책이 필요해요.
Q. 49500(차단된 Client IP) 응답을 받으면 캡챠를 다시 풀 수 있게 해야 하나요?
A. 아니요, 재시도 없이 접속 차단 안내 얼럿을 노출해요.
스토브가 명백한 공격성 IP로 판단해 차단한 경우이므로, 캡챠 재요청은 의미가 없고 오히려 공격 트래픽을 증가시킬 수 있어요.



직접 문의하고 싶으신가요? stove.developers@smilegate.com